
/*-------------------------------------------------------------------------
 *
 * pl_exec.c		- PL/pgSQL 的执行者
 *			  过程语言
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/pl/plpgsql/src/pl_exec.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <ctype.h>

#include "access/detoast.h"
#include "access/htup_details.h"
#include "access/transam.h"
#include "access/tupconvert.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "executor/execExpr.h"
#include "executor/spi.h"
#include "executor/tstoreReceiver.h"
#include "funcapi.h"
#include "mb/stringinfo_mb.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
#include "parser/parse_coerce.h"
#include "parser/parse_type.h"
#include "parser/scansup.h"
#include "plpgsql.h"
#include "storage/proc.h"
#include "tcop/cmdtag.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
#include "tcop/utility.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/typcache.h"

/*
 * 在单个事务内，所有 plpgsql 函数执行共享相同的
 * 执行器 EState 用于评估“简单”表达式。每个函数调用
 * 在此状态下创建自己的“eval_econtext”ExprContext 以便于
 * 每次评估工作区。eval_econtext 在正常函数退出时被释放，
 * 而 EState 在事务结束时被释放（在错误情况下，我们假设
 * 撤销机制会清理所有内容）。此外，函数内部的任何异常
 * 块必须有自己的 eval_econtext，与包含函数的 eval_econtext
 * 分开，以便我们能够在子事务退出时正确清理 ExprContext
 * 回调。我们维护一个堆栈来跟踪各个 econtexts，以便我们
 * 在子事务退出时能够正确清理。
 *
 * 这样的安排有点麻烦，但这是值得的，这样我们就不必在每次
 * 通过函数时重新准备简单表达式。（我们假设要优化的情况
 * 是在一个事务中多次重复调用一个函数。）
 *
 * 然而，在多个 DO 块（内联代码块）执行之间尝试分摊简单
 * 表达式设置是没有价值的，因为这永远不会发生。如果我们
 * 为一个 DO 块使用共享 EState，则表达式状态树将有效地
 * 在事务结束之前泄漏，如果用户不断提交 DO 块，可能会
 * 累积问题。因此，每个 DO 块都有自己的简单表达式 EState，
 * 在退出 plpgsql_inline_handler() 时会被清理。尽管如此，
 * DO 块仍然使用 simple_econtext_stack，以便子事务撤销清理
 * 能够正确进行。
 *
 * （但是，如果一个 DO 块执行 COMMIT 或 ROLLBACK，则
 * exec_stmt_commit 或 exec_stmt_rollback 将从 DO 的简单表达式
 * EState 中取消链接，并创建一个新的共享 EState，此后将使用
 * 该状态。原始 EState 会在返回到 plpgsql_inline_handler 时被
 * 清理。这有点丑陋，但做得更好没有价值，因为这样的场景
 * 不能导致状态树的无限积累。）
 */
typedef struct SimpleEcontextStackEntry
{
	ExprContext *stack_econtext;	/* 叠层 econtext */
	SubTransactionId xact_subxid;	/* 当前子事务的 ID */
	struct SimpleEcontextStackEntry *next;	/* 下一个堆栈条目 */
} SimpleEcontextStackEntry;

static EState *shared_simple_eval_estate = NULL;
static SimpleEcontextStackEntry *simple_econtext_stack = NULL;

/*
 * 除了共享的简单评估 EState，我们还有一个共享资源
 * 所有者，该所有者持有当前事务中任何“简单”表达式
 * 的 CachedPlans 的引用计数。这使我们能够避免在简单
 * 表达式反复使用时不断地获取和释放计划引用计数。
 * （DO 块以与上述共享简单评估状态完全相同的方式使用
 * 自己的资源所有者。）
 */
static ResourceOwner shared_simple_eval_resowner = NULL;

/*
 * plpgsql 函数中的内存管理通常与三个上下文一起工作：
 *
 * 1. 函数调用生命周期数据，例如变量值，保存在“主”上下文中，也称为通过 SPI_connect() 建立的“SPI Proc”上下文。
 * 在这个模块运行代码时，这通常是 CurrentMemoryContext （这并不好，因为粗心的编码很容易导致
 * 函数生命周期内存泄漏，但我们暂时只能这样生活）。
 *
 * 2. 一些语句执行例程需要语句生命周期工作空间。
 * 一个合适的上下文是通过 get_stmt_mcontext() 按需创建的，并且必须在请求例程结束时重置。
 * 错误恢复将自动清理它。 需要语句生命周期工作空间的嵌套语句将导致这样上下文的堆栈，见 push_stmt_mcontext()。
 *
 * 3. 我们使用 eval_econtext 的每元组内存上下文进行表达式评估，并作为短期分配的一般工作空间。
 * 这种分配通常不会显式释放，而是留给上下文重置进行清理，这通常由 exec_eval_cleanup() 完成。
 *
 * 这些宏可用于进行短期分配：
 */
#define get_eval_mcontext(estate) \
	((estate)->eval_econtext->ecxt_per_tuple_memory)
#define eval_mcontext_alloc(estate, sz) \
	MemoryContextAlloc(get_eval_mcontext(estate), sz)
#define eval_mcontext_alloc0(estate, sz) \
	MemoryContextAllocZero(get_eval_mcontext(estate), sz)

/*
 * 我们使用两个会话范围的哈希表来缓存类型转换信息。
 *
 * cast_expr_hash 条目（类型为 plpgsql_CastExprHashEntry）保存转换的编译表达式树。
 * 这些在会话的生命周期内存活，并在所有 PL/pgSQL 函数和 DO 块之间共享。
 * 在某个时刻，可能值得在 pg_cast 变化后使它们失效，但目前我们不打算这样做。
 *
 * 另有一个哈希表 shared_cast_hash（条目的类型为 plpgsql_CastHashEntry），
 * 包含这些表达式的评估状态树，管理方式与简单表达式相同（即，我们假设类型转换表达式始终是简单的）。
 *
 * 与简单表达式一样，DO 块不使用 shared_cast_hash 表，但必须拥有自己的评估状态树。
 * 这并不理想，但我们不想在一个 DO 块内处理多个 simple_eval_estates。
 */
typedef struct					/* 查找类型转换信息的键 */
{
	/* 注意：我们假设这个结构不包含填充字节 */
	Oid			srctype;		/* 源类型用于转换 */
	Oid			dsttype;		/* 目标类型用于转换 */
	int32		srctypmod;		/* 源 typmod 用于转换 */
	int32		dsttypmod;		/* 目标 typmod 用于转换 */
} plpgsql_CastHashKey;

typedef struct					/* cast_expr_hash 表条目 */
{
	plpgsql_CastHashKey key;	/* 哈希键 --- 必须是第一个 */
	Expr	   *cast_expr;		/* 转换表达式，或如果是无操作转换则为 NULL */
	CachedExpression *cast_cexpr;	/* 缓存的表达式支撑上述内容 */
} plpgsql_CastExprHashEntry;

typedef struct					/* cast_hash 表条目 */
{
	plpgsql_CastHashKey key;	/* 哈希键 --- 必须是第一个 */
	plpgsql_CastExprHashEntry *cast_centry; /* 链接到匹配的表达式条目 */
	/* ExprState 只有在 cast_lxid 与当前 LXID 匹配时才有效 */
	ExprState  *cast_exprstate; /* 表达式的评估树 */
	bool		cast_in_use;	/* 在我们执行评估树时为 true */
	LocalTransactionId cast_lxid;
} plpgsql_CastHashEntry;

static HTAB *cast_expr_hash = NULL;
static HTAB *shared_cast_hash = NULL;

/*
 * LOOP_RC_PROCESSING 封装了循环语句的通用逻辑，以处理
 * 循环体语句的返回/退出/继续结果代码。
 * 它的用法如下：
 *
 *		int rc = PLPGSQL_RC_OK;
 *		for (...)
 *		{
 *			...
 *			rc = exec_stmts(estate, stmt->body);
 *			LOOP_RC_PROCESSING(stmt->label, break);
 *			...
 *		}
 *		return rc;
 *
 * 如果循环的执行应该终止，LOOP_RC_PROCESSING 将在更新
 * "rc" 至当前语句应返回的值后执行 "exit_action" 
 * （通常是 "break" 或 "goto"）。如果执行应继续，
 * LOOP_RC_PROCESSING 将不会做任何事情，只是将 "rc" 
 * 重置为 PLPGSQL_RC_OK。
 *
 * estate 和 rc 是宏的隐式参数。
 * estate->exitlabel 会被检查并可能被更新。
 */
#define LOOP_RC_PROCESSING(looplabel, exit_action) \
	if (fc_rc == PLPGSQL_RC_RETURN) \
	{ \
		/* 返回，以便将 RC_RETURN 传播出去 */ \
		exit_action; \
	} \
	else if (fc_rc == PLPGSQL_RC_EXIT) \
	{ \
		if (fc_estate->exitlabel == NULL) \
		{ \
			/* 无标签的 EXIT 终止此循环 */ \
			fc_rc = PLPGSQL_RC_OK; \
			exit_action; \
		} \
		else if ((looplabel) != NULL && \
				 strcmp(looplabel, fc_estate->exitlabel) == 0) \
		{ \
			/* 与此循环匹配的有标签 EXIT，因此终止循环 */ \
			fc_estate->exitlabel = NULL; \
			fc_rc = PLPGSQL_RC_OK; \
			exit_action; \
		} \
		else \
		{ \
			/* 不匹配的有标签 EXIT，传播 RC_EXIT 出去 */ \
			exit_action; \
		} \
	} \
	else if (fc_rc == PLPGSQL_RC_CONTINUE) \
	{ \
		if (fc_estate->exitlabel == NULL) \
		{ \
			/* 无标签的 CONTINUE 匹配此循环，因此在循环中继续 */ \
			fc_rc = PLPGSQL_RC_OK; \
		} \
		else if ((looplabel) != NULL && \
				 strcmp(looplabel, fc_estate->exitlabel) == 0) \
		{ \
			/* 与此循环匹配的有标签 CONTINUE，因此在循环中继续 */ \
			fc_estate->exitlabel = NULL; \
			fc_rc = PLPGSQL_RC_OK; \
		} \
		else \
		{ \
			/* 不匹配的有标签 CONTINUE，传播 RC_CONTINUE 出去 */ \
			exit_action; \
		} \
	} \
	else \
		Assert(fc_rc == PLPGSQL_RC_OK)

/************************************************************
 * 本地函数前向声明
 ************************************************************/
static void fc_coerce_function_result_tuple(PLpgSQL_execstate *fc_estate,
										 TupleDesc fc_tupdesc);
static void fc_plpgsql_exec_error_callback(void *fc_arg);
static void fc_copy_plpgsql_datums(PLpgSQL_execstate *fc_estate,
								PLpgSQL_function *fc_func);
static void fc_plpgsql_fulfill_promise(PLpgSQL_execstate *fc_estate,
									PLpgSQL_var *fc_var);
static MemoryContext fc_get_stmt_mcontext(PLpgSQL_execstate *fc_estate);
static void fc_push_stmt_mcontext(PLpgSQL_execstate *fc_estate);
static void fc_pop_stmt_mcontext(PLpgSQL_execstate *fc_estate);

static int	fc_exec_toplevel_block(PLpgSQL_execstate *fc_estate,
								PLpgSQL_stmt_block *fc_block);
static int	fc_exec_stmt_block(PLpgSQL_execstate *fc_estate,
							PLpgSQL_stmt_block *fc_block);
static int	fc_exec_stmts(PLpgSQL_execstate *fc_estate,
					   List *fc_stmts);
static int	fc_exec_stmt_assign(PLpgSQL_execstate *fc_estate,
							 PLpgSQL_stmt_assign *fc_stmt);
static int	fc_exec_stmt_perform(PLpgSQL_execstate *fc_estate,
							  PLpgSQL_stmt_perform *fc_stmt);
static int	fc_exec_stmt_call(PLpgSQL_execstate *fc_estate,
						   PLpgSQL_stmt_call *fc_stmt);
static int	fc_exec_stmt_getdiag(PLpgSQL_execstate *fc_estate,
							  PLpgSQL_stmt_getdiag *fc_stmt);
static int	fc_exec_stmt_if(PLpgSQL_execstate *fc_estate,
						 PLpgSQL_stmt_if *fc_stmt);
static int	fc_exec_stmt_case(PLpgSQL_execstate *fc_estate,
						   PLpgSQL_stmt_case *fc_stmt);
static int	fc_exec_stmt_loop(PLpgSQL_execstate *fc_estate,
						   PLpgSQL_stmt_loop *fc_stmt);
static int	fc_exec_stmt_while(PLpgSQL_execstate *fc_estate,
							PLpgSQL_stmt_while *fc_stmt);
static int	fc_exec_stmt_fori(PLpgSQL_execstate *fc_estate,
						   PLpgSQL_stmt_fori *fc_stmt);
static int	fc_exec_stmt_fors(PLpgSQL_execstate *fc_estate,
						   PLpgSQL_stmt_fors *fc_stmt);
static int	fc_exec_stmt_forc(PLpgSQL_execstate *fc_estate,
						   PLpgSQL_stmt_forc *fc_stmt);
static int	fc_exec_stmt_foreach_a(PLpgSQL_execstate *fc_estate,
								PLpgSQL_stmt_foreach_a *fc_stmt);
static int	fc_exec_stmt_open(PLpgSQL_execstate *fc_estate,
						   PLpgSQL_stmt_open *fc_stmt);
static int	fc_exec_stmt_fetch(PLpgSQL_execstate *fc_estate,
							PLpgSQL_stmt_fetch *fc_stmt);
static int	fc_exec_stmt_close(PLpgSQL_execstate *fc_estate,
							PLpgSQL_stmt_close *fc_stmt);
static int	fc_exec_stmt_exit(PLpgSQL_execstate *fc_estate,
						   PLpgSQL_stmt_exit *fc_stmt);
static int	fc_exec_stmt_return(PLpgSQL_execstate *fc_estate,
							 PLpgSQL_stmt_return *fc_stmt);
static int	fc_exec_stmt_return_next(PLpgSQL_execstate *fc_estate,
								  PLpgSQL_stmt_return_next *fc_stmt);
static int	fc_exec_stmt_return_query(PLpgSQL_execstate *fc_estate,
								   PLpgSQL_stmt_return_query *fc_stmt);
static int	fc_exec_stmt_raise(PLpgSQL_execstate *fc_estate,
							PLpgSQL_stmt_raise *fc_stmt);
static int	fc_exec_stmt_assert(PLpgSQL_execstate *fc_estate,
							 PLpgSQL_stmt_assert *fc_stmt);
static int	fc_exec_stmt_execsql(PLpgSQL_execstate *fc_estate,
							  PLpgSQL_stmt_execsql *fc_stmt);
static int	fc_exec_stmt_dynexecute(PLpgSQL_execstate *fc_estate,
								 PLpgSQL_stmt_dynexecute *fc_stmt);
static int	fc_exec_stmt_dynfors(PLpgSQL_execstate *fc_estate,
							  PLpgSQL_stmt_dynfors *fc_stmt);
static int	fc_exec_stmt_commit(PLpgSQL_execstate *fc_estate,
							 PLpgSQL_stmt_commit *fc_stmt);
static int	fc_exec_stmt_rollback(PLpgSQL_execstate *fc_estate,
							   PLpgSQL_stmt_rollback *fc_stmt);

static void fc_plpgsql_estate_setup(PLpgSQL_execstate *fc_estate,
								 PLpgSQL_function *fc_func,
								 ReturnSetInfo *fc_rsi,
								 EState *fc_simple_eval_estate,
								 ResourceOwner fc_simple_eval_resowner);
static void fc_exec_eval_cleanup(PLpgSQL_execstate *fc_estate);

static void fc_exec_prepare_plan(PLpgSQL_execstate *fc_estate,
							  PLpgSQL_expr *fc_expr, int fc_cursorOptions);
static void fc_exec_simple_check_plan(PLpgSQL_execstate *fc_estate, PLpgSQL_expr *fc_expr);
static bool fc_exec_is_simple_query(PLpgSQL_expr *fc_expr);
static void fc_exec_save_simple_expr(PLpgSQL_expr *fc_expr, CachedPlan *fc_cplan);
static void fc_exec_check_rw_parameter(PLpgSQL_expr *fc_expr);
static void fc_exec_check_assignable(PLpgSQL_execstate *fc_estate, int fc_dno);
static bool fc_exec_eval_simple_expr(PLpgSQL_execstate *fc_estate,
								  PLpgSQL_expr *fc_expr,
								  Datum *fc_result,
								  bool *fc_isNull,
								  Oid *fc_rettype,
								  int32 *fc_rettypmod);

static void fc_exec_assign_expr(PLpgSQL_execstate *fc_estate,
							 PLpgSQL_datum *fc_target,
							 PLpgSQL_expr *fc_expr);
static void fc_exec_assign_c_string(PLpgSQL_execstate *fc_estate,
								 PLpgSQL_datum *fc_target,
								 const char *fc_str);
static void fc_exec_assign_value(PLpgSQL_execstate *fc_estate,
							  PLpgSQL_datum *fc_target,
							  Datum fc_value, bool fc_isNull,
							  Oid fc_valtype, int32 fc_valtypmod);
static void fc_exec_eval_datum(PLpgSQL_execstate *fc_estate,
							PLpgSQL_datum *fc_datum,
							Oid *fc_typeid,
							int32 *fc_typetypmod,
							Datum *fc_value,
							bool *fc_isnull);
static int	fc_exec_eval_integer(PLpgSQL_execstate *fc_estate,
							  PLpgSQL_expr *fc_expr,
							  bool *fc_isNull);
static bool fc_exec_eval_boolean(PLpgSQL_execstate *fc_estate,
							  PLpgSQL_expr *fc_expr,
							  bool *fc_isNull);
static Datum fc_exec_eval_expr(PLpgSQL_execstate *fc_estate,
							PLpgSQL_expr *fc_expr,
							bool *fc_isNull,
							Oid *fc_rettype,
							int32 *fc_rettypmod);
static int	fc_exec_run_select(PLpgSQL_execstate *fc_estate,
							PLpgSQL_expr *fc_expr, long fc_maxtuples, Portal *fc_portalP);
static int	fc_exec_for_query(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_forq *fc_stmt,
						   Portal fc_portal, bool fc_prefetch_ok);
static ParamListInfo fc_setup_param_list(PLpgSQL_execstate *fc_estate,
									  PLpgSQL_expr *fc_expr);
static ParamExternData *fc_plpgsql_param_fetch(ParamListInfo fc_params,
											int fc_paramid, bool fc_speculative,
											ParamExternData *fc_workspace);
static void fc_plpgsql_param_compile(ParamListInfo fc_params, Param *fc_param,
								  ExprState *fc_state,
								  Datum *fc_resv, bool *fc_resnull);
static void fc_plpgsql_param_eval_var(ExprState *fc_state, ExprEvalStep *fc_op,
								   ExprContext *fc_econtext);
static void fc_plpgsql_param_eval_var_ro(ExprState *fc_state, ExprEvalStep *fc_op,
									  ExprContext *fc_econtext);
static void fc_plpgsql_param_eval_recfield(ExprState *fc_state, ExprEvalStep *fc_op,
										ExprContext *fc_econtext);
static void fc_plpgsql_param_eval_generic(ExprState *fc_state, ExprEvalStep *fc_op,
									   ExprContext *fc_econtext);
static void fc_plpgsql_param_eval_generic_ro(ExprState *fc_state, ExprEvalStep *fc_op,
										  ExprContext *fc_econtext);
static void fc_exec_move_row(PLpgSQL_execstate *fc_estate,
						  PLpgSQL_variable *fc_target,
						  HeapTuple fc_tup, TupleDesc fc_tupdesc);
static void fc_revalidate_rectypeid(PLpgSQL_rec *fc_rec);
static ExpandedRecordHeader *fc_make_expanded_record_for_rec(PLpgSQL_execstate *fc_estate,
														  PLpgSQL_rec *fc_rec,
														  TupleDesc fc_srctupdesc,
														  ExpandedRecordHeader *fc_srcerh);
static void fc_exec_move_row_from_fields(PLpgSQL_execstate *fc_estate,
									  PLpgSQL_variable *fc_target,
									  ExpandedRecordHeader *fc_newerh,
									  Datum *fc_values, bool *fc_nulls,
									  TupleDesc fc_tupdesc);
static bool fc_compatible_tupdescs(TupleDesc fc_src_tupdesc, TupleDesc fc_dst_tupdesc);
static HeapTuple fc_make_tuple_from_row(PLpgSQL_execstate *fc_estate,
									 PLpgSQL_row *fc_row,
									 TupleDesc fc_tupdesc);
static TupleDesc fc_deconstruct_composite_datum(Datum fc_value,
											 HeapTupleData *fc_tmptup);
static void fc_exec_move_row_from_datum(PLpgSQL_execstate *fc_estate,
									 PLpgSQL_variable *fc_target,
									 Datum fc_value);
static void fc_instantiate_empty_record_variable(PLpgSQL_execstate *fc_estate,
											  PLpgSQL_rec *fc_rec);
static char *fc_convert_value_to_string(PLpgSQL_execstate *fc_estate,
									 Datum fc_value, Oid fc_valtype);
static inline Datum fc_exec_cast_value(PLpgSQL_execstate *fc_estate,
									Datum fc_value, bool *fc_isnull,
									Oid fc_valtype, int32 fc_valtypmod,
									Oid fc_reqtype, int32 fc_reqtypmod);
static Datum fc_do_cast_value(PLpgSQL_execstate *fc_estate,
						   Datum fc_value, bool *fc_isnull,
						   Oid fc_valtype, int32 fc_valtypmod,
						   Oid fc_reqtype, int32 fc_reqtypmod);
static plpgsql_CastHashEntry *fc_get_cast_hashentry(PLpgSQL_execstate *fc_estate,
												 Oid fc_srctype, int32 fc_srctypmod,
												 Oid fc_dsttype, int32 fc_dsttypmod);
static void fc_exec_init_tuple_store(PLpgSQL_execstate *fc_estate);
static void fc_exec_set_found(PLpgSQL_execstate *fc_estate, bool fc_state);
static void fc_plpgsql_create_econtext(PLpgSQL_execstate *fc_estate);
static void fc_plpgsql_destroy_econtext(PLpgSQL_execstate *fc_estate);
static void fc_assign_simple_var(PLpgSQL_execstate *fc_estate, PLpgSQL_var *fc_var,
							  Datum fc_newvalue, bool fc_isnull, bool fc_freeable);
static void fc_assign_text_var(PLpgSQL_execstate *fc_estate, PLpgSQL_var *fc_var,
							const char *fc_str);
static void fc_assign_record_var(PLpgSQL_execstate *fc_estate, PLpgSQL_rec *fc_rec,
							  ExpandedRecordHeader *fc_erh);
static ParamListInfo fc_exec_eval_using_params(PLpgSQL_execstate *fc_estate,
											List *fc_params);
static Portal fc_exec_dynquery_with_params(PLpgSQL_execstate *fc_estate,
										PLpgSQL_expr *fc_dynquery, List *fc_params,
										const char *fc_portalname, int fc_cursorOptions);
static char *fc_format_expr_params(PLpgSQL_execstate *fc_estate,
								const PLpgSQL_expr *fc_expr);
static char *fc_format_preparedparamsdata(PLpgSQL_execstate *fc_estate,
									   ParamListInfo fc_paramLI);
static PLpgSQL_variable *fc_make_callstmt_target(PLpgSQL_execstate *fc_estate,
											  PLpgSQL_expr *fc_expr);


/* ----------
 * plpgsql_exec_function 被调用处理程序用于
 *				函数执行。
 *
 * 这也用于执行内联代码块（DO 块）。唯一的
 * 不同之处在于此代码意识到对于 DO 块，我们希望
 * 使用一个私有的 simple_eval_estate 和一个私有的 
 * simple_eval_resowner，这些是由调用者创建并传入的。
 * 对于常规函数，传入 NULL，这意味着使用 
 * shared_simple_eval_estate 和 shared_simple_eval_resowner。
 * （在使用私有 simple_eval_estate 时，
 * 我们还必须使用一个私有的 cast 哈希表，但这在
 * plpgsql_estate_setup 中处理。）
 * procedure_resowner 是一个将在
 * 此函数/过程执行持续期间存活的资源所有者。
 * 只有在进行非原子执行并且函数中有 CALL 或 DO 语句时
 * 它才是必需的；否则它可以是 NULL。我们用它来持有
 * CALL/DO 语句计划的引用计数。
 * ----------
 */
Datum plpgsql_exec_function(PLpgSQL_function *fc_func, FunctionCallInfo fc_fcinfo,
					  EState *fc_simple_eval_estate,
					  ResourceOwner fc_simple_eval_resowner,
					  ResourceOwner fc_procedure_resowner,
					  bool fc_atomic)
{
	PLpgSQL_execstate fc_estate;
	ErrorContextCallback fc_plerrcontext;
	int			fc_i;
	int			fc_rc;

	/*
	 * 设置执行状态
	 */
	fc_plpgsql_estate_setup(&fc_estate, fc_func, (ReturnSetInfo *) fc_fcinfo->resultinfo,
						 fc_simple_eval_estate, fc_simple_eval_resowner);
	fc_estate.procedure_resowner = fc_procedure_resowner;
	fc_estate.atomic = fc_atomic;

	/*
	 * 为 ereport() 设置错误回溯支持。
	 */
	fc_plerrcontext.callback = fc_plpgsql_exec_error_callback;
	fc_plerrcontext.arg = &fc_estate;
	fc_plerrcontext.previous = error_context_stack;
	error_context_stack = &fc_plerrcontext;

	/*
	 * 对所有数据项制作本地执行副本
	 */
	fc_estate.err_text = gettext_noop("during initialization of execution state");
	fc_copy_plpgsql_datums(&fc_estate, fc_func);

	/*
	 * 将实际调用参数值存储到适当的变量中
	 */
	fc_estate.err_text = gettext_noop("while storing call arguments into local variables");
	for (fc_i = 0; fc_i < fc_func->fn_nargs; fc_i++)
	{
		int			fc_n = fc_func->fn_argvarnos[fc_i];

		switch (fc_estate.datums[fc_n]->dtype)
		{
			case PLPGSQL_DTYPE_VAR:
				{
					PLpgSQL_var *fc_var = (PLpgSQL_var *) fc_estate.datums[fc_n];

					fc_assign_simple_var(&fc_estate, fc_var,
									  fc_fcinfo->args[fc_i].value,
									  fc_fcinfo->args[fc_i].isnull,
									  false);

					/*
					 * 强制任何数组值参数以展开形式存储在
					 * 我们的局部变量中，希望提高变量使用的效率。 （这实际上是
					 * 一个hack：为什么只针对数组？需要更多考虑
					 * 哪些情况可能会获胜。 另请参见
					 * typisarray特定启发式方法在exec_assign_value中。）
					 *
					 * 特殊情况：如果传递一个R/W展开指针，假设
					 * 我们可以接管对象而不是复制它。如果传递一个R/O展开指针，暂时将其
					 * 保持为变量的值。 （如果变量被修改，我们会强制
					 * 它为R/W，但这可能根本不会发生。）
					 */
					if (!fc_var->isnull && fc_var->datatype->typisarray)
					{
						if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(fc_var->value)))
						{
							/* 接管R/W对象的所有权 */
							fc_assign_simple_var(&fc_estate, fc_var,
											  TransferExpandedObject(fc_var->value,
																	 fc_estate.datum_context),
											  false,
											  true);
						}
						else if (VARATT_IS_EXTERNAL_EXPANDED_RO(DatumGetPointer(fc_var->value)))
						{
							/* R/O指针，保持原样，直到赋值 */
						}
						else
						{
							/* 扁平数组，因此强制为展开形式 */
							fc_assign_simple_var(&fc_estate, fc_var,
											  expand_array(fc_var->value,
														   fc_estate.datum_context,
														   NULL),
											  false,
											  true);
						}
					}
				}
				break;

			case PLPGSQL_DTYPE_REC:
				{
					PLpgSQL_rec *fc_rec = (PLpgSQL_rec *) fc_estate.datums[fc_n];

					if (!fc_fcinfo->args[fc_i].isnull)
					{
						/* 从复合数据中分配行值 */
						fc_exec_move_row_from_datum(&fc_estate,
												 (PLpgSQL_variable *) fc_rec,
												 fc_fcinfo->args[fc_i].value);
					}
					else
					{
						/* 如果参数为空，则将变量设置为null */
						fc_exec_move_row(&fc_estate, (PLpgSQL_variable *) fc_rec,
									  NULL, NULL);
					}
					/* 在exec_move_row()之后进行清理 */
					fc_exec_eval_cleanup(&fc_estate);
				}
				break;

			default:
				/* 任何其他东西都不应该是参数变量 */
				elog(ERROR, "unrecognized dtype: %d", fc_func->datums[fc_i]->dtype);
		}
	}

	fc_estate.err_text = gettext_noop("during function entry");

	/*
	 * 将魔法变量FOUND设置为false
	 */
	fc_exec_set_found(&fc_estate, false);

	/*
	 * 允许插桩插件查看此函数
	 */
	if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->func_beg)
		((*plpgsql_plugin_ptr)->func_beg) (&fc_estate, fc_func);

	/*
	 * 现在调用顶层语句块
	 */
	fc_estate.err_text = NULL;
	fc_rc = fc_exec_toplevel_block(&fc_estate, fc_func->action);
	if (fc_rc != PLPGSQL_RC_RETURN)
	{
		fc_estate.err_text = NULL;
		ereport(ERROR,
				(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
				 errmsg("control reached end of function without RETURN")));
	}

	/*
	 * 我们得到了返回值 - 处理它
	 */
	fc_estate.err_text = gettext_noop("while casting return value to function's return type");

	fc_fcinfo->isnull = fc_estate.retisnull;

	if (fc_estate.retisset)
	{
		ReturnSetInfo *fc_rsi = fc_estate.rsi;

		/* 检查调用者是否能处理设置结果 */
		if (!fc_rsi || !IsA(fc_rsi, ReturnSetInfo))
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("set-valued function called in context that cannot accept a set")));

		if (!(fc_rsi->allowedModes & SFRM_Materialize))
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("materialize mode required, but it is not allowed in this context")));

		fc_rsi->returnMode = SFRM_Materialize;

		/* 如果我们生成了任何元组，则返回结果 */
		if (fc_estate.tuple_store)
		{
			MemoryContext fc_oldcxt;

			fc_rsi->setResult = fc_estate.tuple_store;
			fc_oldcxt = MemoryContextSwitchTo(fc_estate.tuple_store_cxt);
			fc_rsi->setDesc = CreateTupleDescCopy(fc_estate.tuple_store_desc);
			MemoryContextSwitchTo(fc_oldcxt);
		}
		fc_estate.retval = (Datum) 0;
		fc_fcinfo->isnull = true;
	}
	else if (!fc_estate.retisnull)
	{
		/*
		 * 将结果值转换为函数声明的结果类型，并将其复制
		 * 到上层执行器内存上下文中。 我们必须特别处理元组
		 * 结果，以便处理涉及丢弃列的行类型等情况。
		 */
		if (fc_estate.retistuple)
		{
			/* 如果行类型已知匹配，则不需要强制转换 */
			if (fc_func->fn_rettype == fc_estate.rettype &&
				fc_func->fn_rettype != RECORDOID)
			{
				/*
				 * 将元组结果复制到上层执行器内存上下文中。
				 * 但是，如果我们有一个R/W展开数据项，我们可以直接
				 * 将其所有权转移到上层上下文中。
				 */
				fc_estate.retval = SPI_datumTransfer(fc_estate.retval,
												  false,
												  -1);
			}
			else
			{
				/*
				 * 需要查找预期的结果类型。 XXX 更好的是
				 * 缓存tupdesc，而不是重复
				 * get_call_result_type()，但唯一一个容易保存它的地方
				 * 是在PLpgSQL_function结构中，而这太
				 * 长期存在：复合类型在PLpgSQL_function的存在期间可能会改变。
				 */
				Oid			fc_resultTypeId;
				TupleDesc	fc_tupdesc;

				switch (get_call_result_type(fc_fcinfo, &fc_resultTypeId, &fc_tupdesc))
				{
					case TYPEFUNC_COMPOSITE:
						/* 得到了预期的结果行类型，现在进行强制转换 */
						fc_coerce_function_result_tuple(&fc_estate, fc_tupdesc);
						break;
					case TYPEFUNC_COMPOSITE_DOMAIN:
						/* 得到了预期的结果行类型，现在进行强制转换 */
						fc_coerce_function_result_tuple(&fc_estate, fc_tupdesc);
						/* 并检查域约束 */
						/* XXX 在这里允许缓存也会很好 */
						domain_check(fc_estate.retval, false, fc_resultTypeId,
									 NULL, NULL);
						break;
					case TYPEFUNC_RECORD:

						/*
						 * 未能确定RECORD的实际类型。 我们
						 * 可以在这里引发错误，但这在
						 * 实际上意味着调用者期望任何旧的
						 * 通用行类型，因此我们实际上不需要
						 * 限制。 将生成的结果原样返回。
						 */
						fc_estate.retval = SPI_datumTransfer(fc_estate.retval,
														  false,
														  -1);
						break;
					default:
						/* 如果retistuple为true则不应该到达这里... */
						elog(ERROR, "return type must be a row type");
						break;
				}
			}
		}
		else
		{
			/* 标量情况：使用exec_cast_value */
			fc_estate.retval = fc_exec_cast_value(&fc_estate,
											fc_estate.retval,
											&fc_fcinfo->isnull,
											fc_estate.rettype,
											-1,
											fc_func->fn_rettype,
											-1);

			/*
			 * 如果函数的返回类型不是按值返回，将值
			 * 复制到上层执行器内存上下文中。然而，如果我们有一个 R/W
			 * 扩展数据项，我们可以将其所有权转移到
			 * 上层执行器上下文中。
			 */
			if (!fc_fcinfo->isnull && !fc_func->fn_retbyval)
				fc_estate.retval = SPI_datumTransfer(fc_estate.retval,
												  false,
												  fc_func->fn_rettyplen);
		}
	}
	else
	{
		/*
		 * 我们返回一个 NULL，通常不需要转换工作
		 * 无论数据类型如何。但是，如果我们将其强制转换为域
		 * 返回类型，我们最好检查域的约束是否通过。
		 */
		if (fc_func->fn_retisdomain)
			fc_estate.retval = fc_exec_cast_value(&fc_estate,
											fc_estate.retval,
											&fc_fcinfo->isnull,
											fc_estate.rettype,
											-1,
											fc_func->fn_rettype,
											-1);
	}

	fc_estate.err_text = gettext_noop("during function exit");

	/*
	 * 允许插桩插件查看此函数
	 */
	if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->func_end)
		((*plpgsql_plugin_ptr)->func_end) (&fc_estate, fc_func);

	/* 清理任何剩余的临时内存 */
	fc_plpgsql_destroy_econtext(&fc_estate);
	fc_exec_eval_cleanup(&fc_estate);
	/* stmt_mcontext 将在函数的主上下文被销毁时 */

	/*
	 * 弹出错误上下文堆栈
	 */
	error_context_stack = fc_plerrcontext.previous;

	/*
	 * 返回函数的结果
	 */
	return fc_estate.retval;
}

/*
 * plpgsql_exec_function 的辅助函数：将复合结果强制转换为指定
 * 元组描述符，并复制到上层执行器内存中。这主要是出于美观的
 * 原因分开的——否则逻辑将非常深嵌套。
 *
 * estate->retval 处于就地更新状态。
 */
static void fc_coerce_function_result_tuple(PLpgSQL_execstate *fc_estate, TupleDesc fc_tupdesc)
{
	HeapTuple	fc_rettup;
	TupleDesc	fc_retdesc;
	TupleConversionMap *fc_tupmap;

	/* 我们假设 exec_stmt_return 验证了结果是复合型 */
	Assert(type_is_rowtype(fc_estate->rettype));

	/* 我们可以为扩展记录特例化以提高速度 */
	if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(fc_estate->retval)))
	{
		ExpandedRecordHeader *fc_erh = (ExpandedRecordHeader *) DatumGetEOHP(fc_estate->retval);

		Assert(fc_erh->er_magic == ER_MAGIC);

		/* 提取记录的 TupleDesc */
		fc_retdesc = expanded_record_get_tupdesc(fc_erh);

		/* 检查行类型兼容性 */
		fc_tupmap = convert_tuples_by_position(fc_retdesc,
											fc_tupdesc,
											gettext_noop("returned record type does not match expected record type"));

		/* 可能需要转换 */
		if (fc_tupmap)
		{
			fc_rettup = expanded_record_get_tuple(fc_erh);
			Assert(fc_rettup);
			fc_rettup = execute_attr_map_tuple(fc_rettup, fc_tupmap);

			/*
			 * 将元组复制到上层执行器内存中，作为一个元组 Datum。确保
			 * 它带有调用者提供的元组类型的标签。
			 */
			fc_estate->retval = PointerGetDatum(SPI_returntuple(fc_rettup, fc_tupdesc));
			/* 不需要释放映射，我们即将返回 */
		}
		else if (!(fc_tupdesc->tdtypeid == fc_erh->er_decltypeid ||
				   (fc_tupdesc->tdtypeid == RECORDOID &&
					!ExpandedRecordIsDomain(fc_erh))))
		{
			/*
			 * 扩展记录具有正确的物理 tupdesc，但类型 ID 错误。
			 * （通常，扩展记录是 RECORDOID，但函数声明返回的是命名复合类型。
			 * 如在 exec_move_row_from_datum 中，我们不允许从声明返回
			 * RECORD 的函数返回复合域记录。）因此，我们必须将记录扁平化为元组数据，并
			 * 用正确的东西覆盖其类型字段。spi.c 不提供任何方便的方式来处理
			 * 这种情况，因此我们最终复制 datumCopy() 的主体 :-(
			 */
			Size		fc_resultsize;
			HeapTupleHeader fc_tuphdr;

			fc_resultsize = EOH_get_flat_size(&fc_erh->hdr);
			fc_tuphdr = (HeapTupleHeader) SPI_palloc(fc_resultsize);
			EOH_flatten_into(&fc_erh->hdr, (void *) fc_tuphdr, fc_resultsize);
			HeapTupleHeaderSetTypeId(fc_tuphdr, fc_tupdesc->tdtypeid);
			HeapTupleHeaderSetTypMod(fc_tuphdr, fc_tupdesc->tdtypmod);
			fc_estate->retval = PointerGetDatum(fc_tuphdr);
		}
		else
		{
			/*
			 * 我们只需将结果复制到上层执行器内存上下文中。
			 * 然而，如果我们有一个 R/W 扩展数据项，我们可以直接将
			 * 其所有权转移到上层执行器上下文中。
			 */
			fc_estate->retval = SPI_datumTransfer(fc_estate->retval,
											   false,
											   -1);
		}
	}
	else
	{
		/* 将复合数据项转换为 HeapTuple 和 TupleDesc */
		HeapTupleData fc_tmptup;

		fc_retdesc = fc_deconstruct_composite_datum(fc_estate->retval, &fc_tmptup);
		fc_rettup = &fc_tmptup;

		/* 检查行类型兼容性 */
		fc_tupmap = convert_tuples_by_position(fc_retdesc,
											fc_tupdesc,
											gettext_noop("returned record type does not match expected record type"));

		/* 可能需要转换 */
		if (fc_tupmap)
			fc_rettup = execute_attr_map_tuple(fc_rettup, fc_tupmap);

		/*
		 * 将元组复制到上层执行器内存中，作为一个元组 Datum。确保
		 * 它带有调用者提供的元组类型的标签。
		 */
		fc_estate->retval = PointerGetDatum(SPI_returntuple(fc_rettup, fc_tupdesc));

		/* 不需要释放映射，我们即将返回 */

		ReleaseTupleDesc(fc_retdesc);
	}
}


/* ----------
 * plpgsql_exec_trigger		由触发器执行的调用处理程序调用。
 * ----------
 */
HeapTuple plpgsql_exec_trigger(PLpgSQL_function *fc_func,
					 TriggerData *fc_trigdata)
{
	PLpgSQL_execstate fc_estate;
	ErrorContextCallback fc_plerrcontext;
	int			fc_rc;
	TupleDesc	fc_tupdesc;
	PLpgSQL_rec *fc_rec_new,
			   *fc_rec_old;
	HeapTuple	fc_rettup;

	/*
	 * 设置执行状态
	 */
	fc_plpgsql_estate_setup(&fc_estate, fc_func, NULL, NULL, NULL);
	fc_estate.trigdata = fc_trigdata;

	/*
	 * 为 ereport() 设置错误回溯支持。
	 */
	fc_plerrcontext.callback = fc_plpgsql_exec_error_callback;
	fc_plerrcontext.arg = &fc_estate;
	fc_plerrcontext.previous = error_context_stack;
	error_context_stack = &fc_plerrcontext;

	/*
	 * 对所有数据项制作本地执行副本
	 */
	fc_estate.err_text = gettext_noop("during initialization of execution state");
	fc_copy_plpgsql_datums(&fc_estate, fc_func);

	/*
	 * 将旧的和新的元组放入记录变量中
	 *
	 * 我们为两个变量设置了扩展记录，尽管只有一个可能有值。这允许在用于多种触发器类型的函数中成功进行记录引用。例如，我们可能有一个测试："if (TG_OP = 'INSERT' and NEW.foo = 'xyz')"，这应该在任何当前触发器类型下都能工作。如果实际上从未提供的元组中获取了一个值，它将被读取为NULL。
	 */
	fc_tupdesc = RelationGetDescr(fc_trigdata->tg_relation);

	fc_rec_new = (PLpgSQL_rec *) (fc_estate.datums[fc_func->new_varno]);
	fc_rec_old = (PLpgSQL_rec *) (fc_estate.datums[fc_func->old_varno]);

	fc_rec_new->erh = make_expanded_record_from_tupdesc(fc_tupdesc,
													 fc_estate.datum_context);
	fc_rec_old->erh = make_expanded_record_from_exprecord(fc_rec_new->erh,
													   fc_estate.datum_context);

	if (!TRIGGER_FIRED_FOR_ROW(fc_trigdata->tg_event))
	{
		/*
		 * 每语句触发器不使用旧/新变量
		 */
	}
	else if (TRIGGER_FIRED_BY_INSERT(fc_trigdata->tg_event))
	{
		expanded_record_set_tuple(fc_rec_new->erh, fc_trigdata->tg_trigtuple,
								  false, false);
	}
	else if (TRIGGER_FIRED_BY_UPDATE(fc_trigdata->tg_event))
	{
		expanded_record_set_tuple(fc_rec_new->erh, fc_trigdata->tg_newtuple,
								  false, false);
		expanded_record_set_tuple(fc_rec_old->erh, fc_trigdata->tg_trigtuple,
								  false, false);

		/*
		 * 在BEFORE触发器中，存储的生成列尚未计算，
		 * 因此在NEW行中将它们设置为null。（只能在UPDATE分支中需要；
		 * 在INSERT情况下，它们已经是null，但在UPDATE中，该字段
		 * 仍然包含旧值。）另外，我们可以构造一个没有生成列的
		 * 完整新行结构，但这种方式似乎更有效且潜在地不那么混乱。
		 */
		if (fc_tupdesc->constr && fc_tupdesc->constr->has_generated_stored &&
			TRIGGER_FIRED_BEFORE(fc_trigdata->tg_event))
		{
			for (int fc_i = 0; fc_i < fc_tupdesc->natts; fc_i++)
				if (TupleDescAttr(fc_tupdesc, fc_i)->attgenerated == ATTRIBUTE_GENERATED_STORED)
					expanded_record_set_field_internal(fc_rec_new->erh,
													   fc_i + 1,
													   (Datum) 0,
													   true,	/* isnull */
													   false, false);
		}
	}
	else if (TRIGGER_FIRED_BY_DELETE(fc_trigdata->tg_event))
	{
		expanded_record_set_tuple(fc_rec_old->erh, fc_trigdata->tg_trigtuple,
								  false, false);
	}
	else
		elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, or UPDATE");

	/* 使过渡表对该 SPI 连接可见 */
	fc_rc = SPI_register_trigger_data(fc_trigdata);
	Assert(fc_rc >= 0);

	fc_estate.err_text = gettext_noop("during function entry");

	/*
	 * 将魔法变量FOUND设置为false
	 */
	fc_exec_set_found(&fc_estate, false);

	/*
	 * 允许插桩插件查看此函数
	 */
	if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->func_beg)
		((*plpgsql_plugin_ptr)->func_beg) (&fc_estate, fc_func);

	/*
	 * 现在调用顶层语句块
	 */
	fc_estate.err_text = NULL;
	fc_rc = fc_exec_toplevel_block(&fc_estate, fc_func->action);
	if (fc_rc != PLPGSQL_RC_RETURN)
	{
		fc_estate.err_text = NULL;
		ereport(ERROR,
				(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
				 errmsg("control reached end of trigger procedure without RETURN")));
	}

	fc_estate.err_text = gettext_noop("during function exit");

	if (fc_estate.retisset)
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("trigger procedure cannot return a set")));

	/*
	 * 检查返回的元组结构是否具有触发器触发的关系的相同属性。每个语句触发器总是需要返回NULL，因此我们忽略函数本身产生的任何返回值（XXX：这是个好主意吗？）
	 *
	 * XXX 这样触发器返回一个属性没有正确atttypmod的长度的元组是可能的。确保这不会发生是触发器程序员的责任。Jan
	 */
	if (fc_estate.retisnull || !TRIGGER_FIRED_FOR_ROW(fc_trigdata->tg_event))
		fc_rettup = NULL;
	else
	{
		TupleDesc	fc_retdesc;
		TupleConversionMap *fc_tupmap;

		/* 我们假设 exec_stmt_return 验证了结果是复合型 */
		Assert(type_is_rowtype(fc_estate.rettype));

		/* 我们可以为扩展记录特例化以提高速度 */
		if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(fc_estate.retval)))
		{
			ExpandedRecordHeader *fc_erh = (ExpandedRecordHeader *) DatumGetEOHP(fc_estate.retval);

			Assert(fc_erh->er_magic == ER_MAGIC);

			/* 提取HeapTuple和TupleDesc */
			fc_rettup = expanded_record_get_tuple(fc_erh);
			Assert(fc_rettup);
			fc_retdesc = expanded_record_get_tupdesc(fc_erh);

			if (fc_retdesc != RelationGetDescr(fc_trigdata->tg_relation))
			{
				/* 检查行类型兼容性 */
				fc_tupmap = convert_tuples_by_position(fc_retdesc,
													RelationGetDescr(fc_trigdata->tg_relation),
													gettext_noop("returned row structure does not match the structure of the triggering table"));
				/* 可能需要转换 */
				if (fc_tupmap)
					fc_rettup = execute_attr_map_tuple(fc_rettup, fc_tupmap);
				/* 不需要释放映射，我们即将返回 */
			}

			/*
			 * 将元组复制到上层执行器内存中。但是，如果用户刚刚做了
			 * "return new"或"return old"而没有改变任何东西，就没有
			 * 复制的必要；我们可以返回原始元组（这将
			 * 在trigger.c和这里节省几个周期）。
			 */
			if (fc_rettup != fc_trigdata->tg_newtuple &&
				fc_rettup != fc_trigdata->tg_trigtuple)
				fc_rettup = SPI_copytuple(fc_rettup);
		}
		else
		{
			/* 将复合数据项转换为 HeapTuple 和 TupleDesc */
			HeapTupleData fc_tmptup;

			fc_retdesc = fc_deconstruct_composite_datum(fc_estate.retval, &fc_tmptup);
			fc_rettup = &fc_tmptup;

			/* 检查行类型兼容性 */
			fc_tupmap = convert_tuples_by_position(fc_retdesc,
												RelationGetDescr(fc_trigdata->tg_relation),
												gettext_noop("returned row structure does not match the structure of the triggering table"));
			/* 可能需要转换 */
			if (fc_tupmap)
				fc_rettup = execute_attr_map_tuple(fc_rettup, fc_tupmap);

			ReleaseTupleDesc(fc_retdesc);
			/* 不需要释放映射，我们即将返回 */

			/* 将元组复制到上层执行器内存 */
			fc_rettup = SPI_copytuple(fc_rettup);
		}
	}

	/*
	 * 允许插桩插件查看此函数
	 */
	if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->func_end)
		((*plpgsql_plugin_ptr)->func_end) (&fc_estate, fc_func);

	/* 清理任何剩余的临时内存 */
	fc_plpgsql_destroy_econtext(&fc_estate);
	fc_exec_eval_cleanup(&fc_estate);
	/* stmt_mcontext 将在函数的主上下文被销毁时 */

	/*
	 * 弹出错误上下文堆栈
	 */
	error_context_stack = fc_plerrcontext.previous;

	/*
	 * 返回触发器的结果
	 */
	return fc_rettup;
}

/* ----------
 * plpgsql_exec_event_trigger		由事件触发执行的调用处理程序调用。
 * ----------
 */
void plpgsql_exec_event_trigger(PLpgSQL_function *fc_func, EventTriggerData *fc_trigdata)
{
	PLpgSQL_execstate fc_estate;
	ErrorContextCallback fc_plerrcontext;
	int			fc_rc;

	/*
	 * 设置执行状态
	 */
	fc_plpgsql_estate_setup(&fc_estate, fc_func, NULL, NULL, NULL);
	fc_estate.evtrigdata = fc_trigdata;

	/*
	 * 为 ereport() 设置错误回溯支持。
	 */
	fc_plerrcontext.callback = fc_plpgsql_exec_error_callback;
	fc_plerrcontext.arg = &fc_estate;
	fc_plerrcontext.previous = error_context_stack;
	error_context_stack = &fc_plerrcontext;

	/*
	 * 对所有数据项制作本地执行副本
	 */
	fc_estate.err_text = gettext_noop("during initialization of execution state");
	fc_copy_plpgsql_datums(&fc_estate, fc_func);

	/*
	 * 允许插桩插件查看此函数
	 */
	if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->func_beg)
		((*plpgsql_plugin_ptr)->func_beg) (&fc_estate, fc_func);

	/*
	 * 现在调用顶层语句块
	 */
	fc_estate.err_text = NULL;
	fc_rc = fc_exec_toplevel_block(&fc_estate, fc_func->action);
	if (fc_rc != PLPGSQL_RC_RETURN)
	{
		fc_estate.err_text = NULL;
		ereport(ERROR,
				(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
				 errmsg("control reached end of trigger procedure without RETURN")));
	}

	fc_estate.err_text = gettext_noop("during function exit");

	/*
	 * 允许插桩插件查看此函数
	 */
	if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->func_end)
		((*plpgsql_plugin_ptr)->func_end) (&fc_estate, fc_func);

	/* 清理任何剩余的临时内存 */
	fc_plpgsql_destroy_econtext(&fc_estate);
	fc_exec_eval_cleanup(&fc_estate);
	/* stmt_mcontext 将在函数的主上下文被销毁时 */

	/*
	 * 弹出错误上下文堆栈
	 */
	error_context_stack = fc_plerrcontext.previous;
}

/*
 * 错误上下文回调，以便让我们提供调用栈回溯
 */
static void fc_plpgsql_exec_error_callback(void *fc_arg)
{
	PLpgSQL_execstate *fc_estate = (PLpgSQL_execstate *) fc_arg;
	int			fc_err_lineno;

	/*
	 * 如果 err_var 被设置，报告变量的声明行号。
	 * 否则，如果 err_stmt 被设置，报告 err_stmt 的行号。当
	 * err_stmt 没有被设置时，我们在函数进入/退出中，或
	 * 某个不附加到特定行号的地方。
	 */
	if (fc_estate->err_var != NULL)
		fc_err_lineno = fc_estate->err_var->lineno;
	else if (fc_estate->err_stmt != NULL)
		fc_err_lineno = fc_estate->err_stmt->lineno;
	else
		fc_err_lineno = 0;

	if (fc_estate->err_text != NULL)
	{
		/*
		 * 除非实际需要，否则我们不会花费周期在 err_text 上运行 gettext()。
		 * 因此，设置 err_text 的地方应该使用 gettext_noop() 确保字符串
		 * 被记录在消息字典中。
		 */
		if (fc_err_lineno > 0)
		{
			/*
			 * 翻译者：最后一个 %s 是一个短语，如“在语句块
			 * 本地变量初始化期间”
			 */
			errcontext("PL/pgSQL function %s line %d %s",
					   fc_estate->func->fn_signature,
					   fc_err_lineno,
					   _(fc_estate->err_text));
		}
		else
		{
			/*
			 * 翻译者：最后一个 %s 是一个短语，如“在将调用
			 * 参数存储到局部变量时”
			 */
			errcontext("PL/pgSQL function %s %s",
					   fc_estate->func->fn_signature,
					   _(fc_estate->err_text));
		}
	}
	else if (fc_estate->err_stmt != NULL && fc_err_lineno > 0)
	{
		/* 翻译者：最后一个 %s 是一个 plpgsql 语句类型名称 */
		errcontext("PL/pgSQL function %s line %d at %s",
				   fc_estate->func->fn_signature,
				   fc_err_lineno,
				   plpgsql_stmt_typename(fc_estate->err_stmt));
	}
	else
		errcontext("PL/pgSQL function %s",
				   fc_estate->func->fn_signature);
}


/* ----------
 * 初始化局部执行变量的支持函数
 * ----------
 */
static void fc_copy_plpgsql_datums(PLpgSQL_execstate *fc_estate,
					PLpgSQL_function *fc_func)
{
	int			fc_ndatums = fc_estate->ndatums;
	PLpgSQL_datum **fc_indatums;
	PLpgSQL_datum **fc_outdatums;
	char	   *fc_workspace;
	char	   *fc_ws_next;
	int			fc_i;

	/* 分配局部数据项指针数组 */
	fc_estate->datums = (PLpgSQL_datum **)
		palloc(sizeof(PLpgSQL_datum *) * fc_ndatums);

	/*
	 * 为减少 palloc 开销，我们为所有局部实例化的数据项所需的
	 * 空间进行一次 palloc 请求。
	 */
	fc_workspace = palloc(fc_func->copiable_size);
	fc_ws_next = fc_workspace;

	/* 填充数据项指针数组，根据需要将数据项复制到工作区 */
	fc_indatums = fc_func->datums;
	fc_outdatums = fc_estate->datums;
	for (fc_i = 0; fc_i < fc_ndatums; fc_i++)
	{
		PLpgSQL_datum *fc_indatum = fc_indatums[fc_i];
		PLpgSQL_datum *fc_outdatum;

		/* 这必须与 plpgsql_finish_datums 在可复制的内容上达成一致 */
		switch (fc_indatum->dtype)
		{
			case PLPGSQL_DTYPE_VAR:
			case PLPGSQL_DTYPE_PROMISE:
				fc_outdatum = (PLpgSQL_datum *) fc_ws_next;
				memcpy(fc_outdatum, fc_indatum, sizeof(PLpgSQL_var));
				fc_ws_next += MAXALIGN(sizeof(PLpgSQL_var));
				break;

			case PLPGSQL_DTYPE_REC:
				fc_outdatum = (PLpgSQL_datum *) fc_ws_next;
				memcpy(fc_outdatum, fc_indatum, sizeof(PLpgSQL_rec));
				fc_ws_next += MAXALIGN(sizeof(PLpgSQL_rec));
				break;

			case PLPGSQL_DTYPE_ROW:
			case PLPGSQL_DTYPE_RECFIELD:

				/*
				 * 这些数据项记录在运行时是只读的，因此无需
				 * 复制它们（好吧，RECFIELD 包含缓存数据，但
				 * 我们反正希望集中缓存）。
				 */
				fc_outdatum = fc_indatum;
				break;

			default:
				elog(ERROR, "unrecognized dtype: %d", fc_indatum->dtype);
				fc_outdatum = NULL;	/* 保持编译器安静 */
				break;
		}

		fc_outdatums[fc_i] = fc_outdatum;
	}

	Assert(fc_ws_next == fc_workspace + fc_func->copiable_size);
}

/*
 * 如果变量有一个激活的“承诺”，计算承诺的值
 * 并将其分配给变量。
 * 赋值会自动解除承诺。
 */
static void fc_plpgsql_fulfill_promise(PLpgSQL_execstate *fc_estate,
						PLpgSQL_var *fc_var)
{
	MemoryContext fc_oldcontext;

	if (fc_var->promise == PLPGSQL_PROMISE_NONE)
		return;					/* 无需操作 */

	/*
	 * 这通常将在短暂的上下文中调用，例如 mcontext。
	 * 我们必须在 estate 的数据项上下文中创建变量值。
	 * 这种快速且肮脏的解决方案存在泄漏一些额外垃圾的风险，
	 * 但由于每个承诺在每次函数调用中最多被尊重一次，
	 * 因此可能不值得更加小心。
	 */
	fc_oldcontext = MemoryContextSwitchTo(fc_estate->datum_context);

	switch (fc_var->promise)
	{
		case PLPGSQL_PROMISE_TG_NAME:
			if (fc_estate->trigdata == NULL)
				elog(ERROR, "trigger promise is not in a trigger function");
			fc_assign_simple_var(fc_estate, fc_var,
							  DirectFunctionCall1(namein,
												  CStringGetDatum(fc_estate->trigdata->tg_trigger->tgname)),
							  false, true);
			break;

		case PLPGSQL_PROMISE_TG_WHEN:
			if (fc_estate->trigdata == NULL)
				elog(ERROR, "trigger promise is not in a trigger function");
			if (TRIGGER_FIRED_BEFORE(fc_estate->trigdata->tg_event))
				fc_assign_text_var(fc_estate, fc_var, "BEFORE");
			else if (TRIGGER_FIRED_AFTER(fc_estate->trigdata->tg_event))
				fc_assign_text_var(fc_estate, fc_var, "AFTER");
			else if (TRIGGER_FIRED_INSTEAD(fc_estate->trigdata->tg_event))
				fc_assign_text_var(fc_estate, fc_var, "INSTEAD OF");
			else
				elog(ERROR, "unrecognized trigger execution time: not BEFORE, AFTER, or INSTEAD OF");
			break;

		case PLPGSQL_PROMISE_TG_LEVEL:
			if (fc_estate->trigdata == NULL)
				elog(ERROR, "trigger promise is not in a trigger function");
			if (TRIGGER_FIRED_FOR_ROW(fc_estate->trigdata->tg_event))
				fc_assign_text_var(fc_estate, fc_var, "ROW");
			else if (TRIGGER_FIRED_FOR_STATEMENT(fc_estate->trigdata->tg_event))
				fc_assign_text_var(fc_estate, fc_var, "STATEMENT");
			else
				elog(ERROR, "unrecognized trigger event type: not ROW or STATEMENT");
			break;

		case PLPGSQL_PROMISE_TG_OP:
			if (fc_estate->trigdata == NULL)
				elog(ERROR, "trigger promise is not in a trigger function");
			if (TRIGGER_FIRED_BY_INSERT(fc_estate->trigdata->tg_event))
				fc_assign_text_var(fc_estate, fc_var, "INSERT");
			else if (TRIGGER_FIRED_BY_UPDATE(fc_estate->trigdata->tg_event))
				fc_assign_text_var(fc_estate, fc_var, "UPDATE");
			else if (TRIGGER_FIRED_BY_DELETE(fc_estate->trigdata->tg_event))
				fc_assign_text_var(fc_estate, fc_var, "DELETE");
			else if (TRIGGER_FIRED_BY_TRUNCATE(fc_estate->trigdata->tg_event))
				fc_assign_text_var(fc_estate, fc_var, "TRUNCATE");
			else
				elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, UPDATE, or TRUNCATE");
			break;

		case PLPGSQL_PROMISE_TG_RELID:
			if (fc_estate->trigdata == NULL)
				elog(ERROR, "trigger promise is not in a trigger function");
			fc_assign_simple_var(fc_estate, fc_var,
							  ObjectIdGetDatum(fc_estate->trigdata->tg_relation->rd_id),
							  false, false);
			break;

		case PLPGSQL_PROMISE_TG_TABLE_NAME:
			if (fc_estate->trigdata == NULL)
				elog(ERROR, "trigger promise is not in a trigger function");
			fc_assign_simple_var(fc_estate, fc_var,
							  DirectFunctionCall1(namein,
												  CStringGetDatum(RelationGetRelationName(fc_estate->trigdata->tg_relation))),
							  false, true);
			break;

		case PLPGSQL_PROMISE_TG_TABLE_SCHEMA:
			if (fc_estate->trigdata == NULL)
				elog(ERROR, "trigger promise is not in a trigger function");
			fc_assign_simple_var(fc_estate, fc_var,
							  DirectFunctionCall1(namein,
												  CStringGetDatum(get_namespace_name(RelationGetNamespace(fc_estate->trigdata->tg_relation)))),
							  false, true);
			break;

		case PLPGSQL_PROMISE_TG_NARGS:
			if (fc_estate->trigdata == NULL)
				elog(ERROR, "trigger promise is not in a trigger function");
			fc_assign_simple_var(fc_estate, fc_var,
							  Int16GetDatum(fc_estate->trigdata->tg_trigger->tgnargs),
							  false, false);
			break;

		case PLPGSQL_PROMISE_TG_ARGV:
			if (fc_estate->trigdata == NULL)
				elog(ERROR, "trigger promise is not in a trigger function");
			if (fc_estate->trigdata->tg_trigger->tgnargs > 0)
			{
				/*
				 * 出于历史原因，tg_argv[] 下标从零开始而不是从一开始。
				 * 所以我们无法使用 construct_array()。
				 */
				int			fc_nelems = fc_estate->trigdata->tg_trigger->tgnargs;
				Datum	   *fc_elems;
				int			fc_dims[1];
				int			fc_lbs[1];
				int			fc_i;

				fc_elems = palloc(sizeof(Datum) * fc_nelems);
				for (fc_i = 0; fc_i < fc_nelems; fc_i++)
					fc_elems[fc_i] = CStringGetTextDatum(fc_estate->trigdata->tg_trigger->tgargs[fc_i]);
				fc_dims[0] = fc_nelems;
				fc_lbs[0] = 0;

				fc_assign_simple_var(fc_estate, fc_var,
								  PointerGetDatum(construct_md_array(fc_elems, NULL,
																	 1, fc_dims, fc_lbs,
																	 TEXTOID,
																	 -1, false, TYPALIGN_INT)),
								  false, true);
			}
			else
			{
				fc_assign_simple_var(fc_estate, fc_var, (Datum) 0, true, false);
			}
			break;

		case PLPGSQL_PROMISE_TG_EVENT:
			if (fc_estate->evtrigdata == NULL)
				elog(ERROR, "event trigger promise is not in an event trigger function");
			fc_assign_text_var(fc_estate, fc_var, fc_estate->evtrigdata->event);
			break;

		case PLPGSQL_PROMISE_TG_TAG:
			if (fc_estate->evtrigdata == NULL)
				elog(ERROR, "event trigger promise is not in an event trigger function");
			fc_assign_text_var(fc_estate, fc_var, GetCommandTagName(fc_estate->evtrigdata->tag));
			break;

		default:
			elog(ERROR, "unrecognized promise type: %d", fc_var->promise);
	}

	MemoryContextSwitchTo(fc_oldcontext);
}

/*
 * 创建一个用于语句生命周期变量的内存上下文，如果我们还没有一个的话。它将是stmt_mcontext_parent的子上下文，后者要么是函数的主要上下文，要么是向下推送的外部stmt_mcontext。
 */
static MemoryContext fc_get_stmt_mcontext(PLpgSQL_execstate *fc_estate)
{
	if (fc_estate->stmt_mcontext == NULL)
	{
		fc_estate->stmt_mcontext =
			AllocSetContextCreate(fc_estate->stmt_mcontext_parent,
								  "PLpgSQL per-statement data",
								  ALLOCSET_DEFAULT_SIZES);
	}
	return fc_estate->stmt_mcontext;
}

/*
 * 向下推送当前的stmt_mcontext，以便被调用的语句不会使用它。这是由具有语句生命周期数据并需要在某些内部语句之间保留这些数据的语句所需的。调用者最终应该执行pop_stmt_mcontext()。
 */
static void fc_push_stmt_mcontext(PLpgSQL_execstate *fc_estate)
{
	/* 应该先执行get_stmt_mcontext() */
	Assert(fc_estate->stmt_mcontext != NULL);
	/* 断言我们没有搞乱堆栈链接 */
	Assert(MemoryContextGetParent(fc_estate->stmt_mcontext) == fc_estate->stmt_mcontext_parent);
	/* 向下推送以成为任何嵌套stmt mcontext的父上下文 */
	fc_estate->stmt_mcontext_parent = fc_estate->stmt_mcontext;
	/* 并使其不可直接使用 */
	fc_estate->stmt_mcontext = NULL;
}

/*
 * 撤销push_stmt_mcontext()。我们假设这在重置调用者的stmt_mcontext之前或之后执行；因为该操作还会删除任何子上下文，所以不需要显式删除当前可能是estate->stmt_mcontext的上下文。
 */
static void fc_pop_stmt_mcontext(PLpgSQL_execstate *fc_estate)
{
	/* 我们只需要弹出栈 */
	fc_estate->stmt_mcontext = fc_estate->stmt_mcontext_parent;
	fc_estate->stmt_mcontext_parent = MemoryContextGetParent(fc_estate->stmt_mcontext);
}


/*
 * exec_stmt_block的子程序：条件列表中的任何条件是否与当前异常匹配？
 */
static bool fc_exception_matches_conditions(ErrorData *fc_edata, PLpgSQL_condition *fc_cond)
{
	for (; fc_cond != NULL; fc_cond = fc_cond->next)
	{
		int			fc_sqlerrstate = fc_cond->sqlerrstate;

		/*
		 * OTHERS匹配除了查询取消和断言失败之外的所有内容。如果你足够愚蠢，可以明确匹配这些。
		 */
		if (fc_sqlerrstate == 0)
		{
			if (fc_edata->sqlerrcode != ERRCODE_QUERY_CANCELED &&
				fc_edata->sqlerrcode != ERRCODE_ASSERT_FAILURE)
				return true;
		}
		/* 精确匹配？ */
		else if (fc_edata->sqlerrcode == fc_sqlerrstate)
			return true;
		/* 类别匹配？ */
		else if (ERRCODE_IS_CATEGORY(fc_sqlerrstate) &&
				 ERRCODE_TO_CATEGORY(fc_edata->sqlerrcode) == fc_sqlerrstate)
			return true;
	}
	return false;
}


/* ----------
 * exec_toplevel_block			执行顶层块
 *
 * 这故意等同于执行exec_stmts()，其列表由一个语句组成。一个小的区别是我们不必保存estate->err_stmt的入口值；这被假定为NULL。
 * ----------
 */
static int fc_exec_toplevel_block(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_block *fc_block)
{
	int			fc_rc;

	fc_estate->err_stmt = (PLpgSQL_stmt *) fc_block;

	/* 让插件知道我们即将执行此语句 */
	if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->stmt_beg)
		((*plpgsql_plugin_ptr)->stmt_beg) (fc_estate, (PLpgSQL_stmt *) fc_block);

	CHECK_FOR_INTERRUPTS();

	fc_rc = fc_exec_stmt_block(fc_estate, fc_block);

	/* 让插件知道我们已经完成执行此语句 */
	if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->stmt_end)
		((*plpgsql_plugin_ptr)->stmt_end) (fc_estate, (PLpgSQL_stmt *) fc_block);

	fc_estate->err_stmt = NULL;

	return fc_rc;
}


/* ----------
 * exec_stmt_block			执行一组语句
 * ----------
 */
static int fc_exec_stmt_block(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_block *fc_block)
{
	volatile int fc_rc = -1;
	int			fc_i;

	/*
	 * 首先初始化此块中声明的所有变量
	 */
	fc_estate->err_text = gettext_noop("during statement block local variable initialization");

	for (fc_i = 0; fc_i < fc_block->n_initvars; fc_i++)
	{
		int			fc_n = fc_block->initvarnos[fc_i];
		PLpgSQL_datum *fc_datum = fc_estate->datums[fc_n];

		/*
		 * 此处处理的dtypes集合必须与plpgsql_add_initdatums()匹配。
		 *
		 * 注意，我们当前不支持块中的promise datums，仅在函数的最外层作用域中支持，因此我们不需要在这里处理它们。
		 *
		 * 由于RECFIELD也不支持，因此将PLpgSQL_datum强制转换为PLpgSQL_variable是可以的。
		 */
		fc_estate->err_var = (PLpgSQL_variable *) fc_datum;

		switch (fc_datum->dtype)
		{
			case PLPGSQL_DTYPE_VAR:
				{
					PLpgSQL_var *fc_var = (PLpgSQL_var *) fc_datum;

					/*
					 * 释放任何旧值，以防重新进入块，并初始化为NULL
					 */
					fc_assign_simple_var(fc_estate, fc_var, (Datum) 0, true, false);

					if (fc_var->default_val == NULL)
					{
						/*
						 * 如有必要，通过将NULL分配给变量，给予数据类型拒绝NULL的机会。我们声称该值的类型为UNKNOWN，而不是变量的数据类型，否则将跳过强制转换。
						 */
						if (fc_var->datatype->typtype == TYPTYPE_DOMAIN)
							fc_exec_assign_value(fc_estate,
											  (PLpgSQL_datum *) fc_var,
											  (Datum) 0,
											  true,
											  UNKNOWNOID,
											  -1);

						/* 解析器应该拒绝NOT NULL */
						Assert(!fc_var->notnull);
					}
					else
					{
						fc_exec_assign_expr(fc_estate, (PLpgSQL_datum *) fc_var,
										 fc_var->default_val);
					}
				}
				break;

			case PLPGSQL_DTYPE_REC:
				{
					PLpgSQL_rec *fc_rec = (PLpgSQL_rec *) fc_datum;

					/*
					 * 任何现有对象的删除将在下面的赋值期间处理，在某些情况下，对于我们来说，提前处理它并不高效。
					 */
					if (fc_rec->default_val == NULL)
					{
						/*
						 * 如果需要，通过将 NULL 分配给变量，给数据类型拒绝
						 * NULL 的机会。
						 */
						fc_exec_move_row(fc_estate, (PLpgSQL_variable *) fc_rec,
									  NULL, NULL);

						/* 解析器应该拒绝NOT NULL */
						Assert(!fc_rec->notnull);
					}
					else
					{
						fc_exec_assign_expr(fc_estate, (PLpgSQL_datum *) fc_rec,
										 fc_rec->default_val);
					}
				}
				break;

			default:
				elog(ERROR, "unrecognized dtype: %d", fc_datum->dtype);
		}
	}

	fc_estate->err_var = NULL;

	if (fc_block->exceptions)
	{
		/*
		 * 在子事务中执行块体内的语句
		 */
		MemoryContext fc_oldcontext = CurrentMemoryContext;
		ResourceOwner fc_oldowner = CurrentResourceOwner;
		ExprContext *fc_old_eval_econtext = fc_estate->eval_econtext;
		ErrorData  *fc_save_cur_error = fc_estate->cur_error;
		MemoryContext fc_stmt_mcontext;

		fc_estate->err_text = gettext_noop("during statement block entry");

		/*
		 * 如果发生错误，我们将需要一个 stmt_mcontext 来持有错误数据。
		 * 在进入子事务之前强制它存在似乎是最好的选择，这样可以减少
		 * 在错误恢复期间内存不足的风险，并且这大大简化了在错误后
		 * 将 stmt_mcontext 堆栈恢复到正确状态的过程。我们可以通过
		 * 允许被调用的语句也使用该 mcontext 来缓解这种成本；所以
		 * 我们不在这里将其推入。
		 */
		fc_stmt_mcontext = fc_get_stmt_mcontext(fc_estate);

		BeginInternalSubTransaction(NULL);
		/* 希望在函数的内存上下文中运行语句 */
		MemoryContextSwitchTo(fc_oldcontext);

		PG_TRY();
		{
			/*
			 * 我们需要在属于当前子事务的新 eval_econtext 中运行
			 * 块的语句；如果我们尝试使用外部 econtext，则
			 * ExprContext 关闭回调将在错误的时间被调用。
			 */
			fc_plpgsql_create_econtext(fc_estate);

			fc_estate->err_text = NULL;

			/* 运行块的语句 */
			fc_rc = fc_exec_stmts(fc_estate, fc_block->body);

			fc_estate->err_text = gettext_noop("during statement block exit");

			/*
			 * 如果块以 RETURN 结束，我们可能需要从子事务
			 * eval_context 中复制返回值。如果该值恰好是
			 * R/W 扩展对象，我们可以避免物理复制。
			 */
			if (fc_rc == PLPGSQL_RC_RETURN &&
				!fc_estate->retisset &&
				!fc_estate->retisnull)
			{
				int16		fc_resTypLen;
				bool		fc_resTypByVal;

				get_typlenbyval(fc_estate->rettype, &fc_resTypLen, &fc_resTypByVal);
				fc_estate->retval = datumTransfer(fc_estate->retval,
											   fc_resTypByVal, fc_resTypLen);
			}

			/* 提交内部事务，返回外部事务上下文 */
			ReleaseCurrentSubTransaction();
			MemoryContextSwitchTo(fc_oldcontext);
			CurrentResourceOwner = fc_oldowner;

			/* 断言 stmt_mcontext 堆栈保持不变 */
			Assert(fc_stmt_mcontext == fc_estate->stmt_mcontext);

			/*
			 * 恢复到外部 eval_econtext。（内部的在子事务退出时
			 * 被自动清理。）
			 */
			fc_estate->eval_econtext = fc_old_eval_econtext;
		}
		PG_CATCH();
		{
			ErrorData  *fc_edata;
			ListCell   *fc_e;

			fc_estate->err_text = gettext_noop("during exception cleanup");

			/* 在我们的 stmt_mcontext 中保存错误信息 */
			MemoryContextSwitchTo(fc_stmt_mcontext);
			fc_edata = CopyErrorData();
			FlushErrorState();

			/* 中止内部事务 */
			RollbackAndReleaseCurrentSubTransaction();
			MemoryContextSwitchTo(fc_oldcontext);
			CurrentResourceOwner = fc_oldowner;

			/*
			 * 设置 stmt_mcontext 堆栈，好像我们恢复了之前的
			 * 状态，然后执行了 push_stmt_mcontext()。进行推入
			 * 是必要的，以便异常处理程序中的语句不会干扰
			 * 在我们的 stmt_mcontext 中的错误数据。
			 */
			fc_estate->stmt_mcontext_parent = fc_stmt_mcontext;
			fc_estate->stmt_mcontext = NULL;

			/*
			 * 现在我们可以删除任何可能作为我们的子项创建的嵌套
			 * stmt_mcontexts。（注意：我们不会立即释放在
			 * stmt_mcontext 中可能留下的任何语句生命周期数据。
			 * 我们可以通过在收集上述错误数据之前对其执行
			 * MemoryContextReset 来尝试这样做，但在收集错误
			 * 之前执行任何大量工作似乎太冒险了。）
			 */
			MemoryContextDeleteChildren(fc_stmt_mcontext);

			/* 恢复到外部 eval_econtext */
			fc_estate->eval_econtext = fc_old_eval_econtext;

			/*
			 * 也必须清理 econtext。然而，在子事务中创建的任何
			 * 元组表将在子事务中由于 SPI 的中止而被丢弃，所以
			 * 我们不需要（也不能尝试）释放 eval_tuptable。
			 */
			fc_estate->eval_tuptable = NULL;
			fc_exec_eval_cleanup(fc_estate);

			/* 查找匹配的异常处理程序 */
			foreach(fc_e, fc_block->exceptions->exc_list)
			{
				PLpgSQL_exception *fc_exception = (PLpgSQL_exception *) lfirst(fc_e);

				if (fc_exception_matches_conditions(fc_edata, fc_exception->conditions))
				{
					/*
					 * 初始化对于异常块的魔法 SQLSTATE 和 SQLERRM 变量；
					 * 这也会释放之前使用同一异常的值。我们不需要在
					 * 找到匹配的异常之前进行此操作。
					 */
					PLpgSQL_var *fc_state_var;
					PLpgSQL_var *fc_errm_var;

					fc_state_var = (PLpgSQL_var *)
						fc_estate->datums[fc_block->exceptions->sqlstate_varno];
					fc_errm_var = (PLpgSQL_var *)
						fc_estate->datums[fc_block->exceptions->sqlerrm_varno];

					fc_assign_text_var(fc_estate, fc_state_var,
									unpack_sql_state(fc_edata->sqlerrcode));
					fc_assign_text_var(fc_estate, fc_errm_var, fc_edata->message);

					/*
				 * 还要设置 cur_error，以便在处理程序内部可以访问错误数据。
				 */
					fc_estate->cur_error = fc_edata;

					fc_estate->err_text = NULL;

					fc_rc = fc_exec_stmts(fc_estate, fc_exception->action);

					break;
				}
			}

			/*
			 * 恢复 cur_error 的先前状态，无论我们是否执行了处理程序。 
			 * 这在某个内部块的异常处理程序抛出错误的情况下是必需的。
			 */
			fc_estate->cur_error = fc_save_cur_error;

			/* 如果没有找到匹配，则重新抛出错误 */
			if (fc_e == NULL)
				ReThrowError(fc_edata);

			/* 恢复 stmt_mcontext 堆栈并释放错误数据 */
			fc_pop_stmt_mcontext(fc_estate);
			MemoryContextReset(fc_stmt_mcontext);
		}
		PG_END_TRY();

		Assert(fc_save_cur_error == fc_estate->cur_error);
	}
	else
	{
		/*
			 * 只执行块主体中的语句
			 */
		fc_estate->err_text = NULL;

		fc_rc = fc_exec_stmts(fc_estate, fc_block->body);
	}

	fc_estate->err_text = NULL;

	/*
	 * 处理返回代码。这 intentionally 与 LOOP_RC_PROCESSING() 不同：
	 * CONTINUE 永远不会匹配一个块，而 EXIT 只有在标签匹配时才匹配一个块。
	 */
	switch (fc_rc)
	{
		case PLPGSQL_RC_OK:
		case PLPGSQL_RC_RETURN:
		case PLPGSQL_RC_CONTINUE:
			return fc_rc;

		case PLPGSQL_RC_EXIT:
			if (fc_estate->exitlabel == NULL)
				return PLPGSQL_RC_EXIT;
			if (fc_block->label == NULL)
				return PLPGSQL_RC_EXIT;
			if (strcmp(fc_block->label, fc_estate->exitlabel) != 0)
				return PLPGSQL_RC_EXIT;
			fc_estate->exitlabel = NULL;
			return PLPGSQL_RC_OK;

		default:
			elog(ERROR, "unrecognized rc: %d", fc_rc);
	}

	return PLPGSQL_RC_OK;
}


/* ----------
 * exec_stmts			迭代一系列语句
 *				只要它们的返回代码是 OK
 * ----------
 */
static int fc_exec_stmts(PLpgSQL_execstate *fc_estate, List *fc_stmts)
{
	PLpgSQL_stmt *fc_save_estmt = fc_estate->err_stmt;
	ListCell   *fc_s;

	if (fc_stmts == NIL)
	{
		/*
			 * 确保我们即使没有语句也执行 CHECK_FOR_INTERRUPTS()。 
			 * 这可以防止在紧密循环中挂起，例如当有一个空主体的 LOOP 结构时。
			 */
		CHECK_FOR_INTERRUPTS();
		return PLPGSQL_RC_OK;
	}

	foreach(fc_s, fc_stmts)
	{
		PLpgSQL_stmt *fc_stmt = (PLpgSQL_stmt *) lfirst(fc_s);
		int			fc_rc;

		fc_estate->err_stmt = fc_stmt;

		/* 让插件知道我们即将执行此语句 */
		if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->stmt_beg)
			((*plpgsql_plugin_ptr)->stmt_beg) (fc_estate, fc_stmt);

		CHECK_FOR_INTERRUPTS();

		switch (fc_stmt->cmd_type)
		{
			case PLPGSQL_STMT_BLOCK:
				fc_rc = fc_exec_stmt_block(fc_estate, (PLpgSQL_stmt_block *) fc_stmt);
				break;

			case PLPGSQL_STMT_ASSIGN:
				fc_rc = fc_exec_stmt_assign(fc_estate, (PLpgSQL_stmt_assign *) fc_stmt);
				break;

			case PLPGSQL_STMT_PERFORM:
				fc_rc = fc_exec_stmt_perform(fc_estate, (PLpgSQL_stmt_perform *) fc_stmt);
				break;

			case PLPGSQL_STMT_CALL:
				fc_rc = fc_exec_stmt_call(fc_estate, (PLpgSQL_stmt_call *) fc_stmt);
				break;

			case PLPGSQL_STMT_GETDIAG:
				fc_rc = fc_exec_stmt_getdiag(fc_estate, (PLpgSQL_stmt_getdiag *) fc_stmt);
				break;

			case PLPGSQL_STMT_IF:
				fc_rc = fc_exec_stmt_if(fc_estate, (PLpgSQL_stmt_if *) fc_stmt);
				break;

			case PLPGSQL_STMT_CASE:
				fc_rc = fc_exec_stmt_case(fc_estate, (PLpgSQL_stmt_case *) fc_stmt);
				break;

			case PLPGSQL_STMT_LOOP:
				fc_rc = fc_exec_stmt_loop(fc_estate, (PLpgSQL_stmt_loop *) fc_stmt);
				break;

			case PLPGSQL_STMT_WHILE:
				fc_rc = fc_exec_stmt_while(fc_estate, (PLpgSQL_stmt_while *) fc_stmt);
				break;

			case PLPGSQL_STMT_FORI:
				fc_rc = fc_exec_stmt_fori(fc_estate, (PLpgSQL_stmt_fori *) fc_stmt);
				break;

			case PLPGSQL_STMT_FORS:
				fc_rc = fc_exec_stmt_fors(fc_estate, (PLpgSQL_stmt_fors *) fc_stmt);
				break;

			case PLPGSQL_STMT_FORC:
				fc_rc = fc_exec_stmt_forc(fc_estate, (PLpgSQL_stmt_forc *) fc_stmt);
				break;

			case PLPGSQL_STMT_FOREACH_A:
				fc_rc = fc_exec_stmt_foreach_a(fc_estate, (PLpgSQL_stmt_foreach_a *) fc_stmt);
				break;

			case PLPGSQL_STMT_EXIT:
				fc_rc = fc_exec_stmt_exit(fc_estate, (PLpgSQL_stmt_exit *) fc_stmt);
				break;

			case PLPGSQL_STMT_RETURN:
				fc_rc = fc_exec_stmt_return(fc_estate, (PLpgSQL_stmt_return *) fc_stmt);
				break;

			case PLPGSQL_STMT_RETURN_NEXT:
				fc_rc = fc_exec_stmt_return_next(fc_estate, (PLpgSQL_stmt_return_next *) fc_stmt);
				break;

			case PLPGSQL_STMT_RETURN_QUERY:
				fc_rc = fc_exec_stmt_return_query(fc_estate, (PLpgSQL_stmt_return_query *) fc_stmt);
				break;

			case PLPGSQL_STMT_RAISE:
				fc_rc = fc_exec_stmt_raise(fc_estate, (PLpgSQL_stmt_raise *) fc_stmt);
				break;

			case PLPGSQL_STMT_ASSERT:
				fc_rc = fc_exec_stmt_assert(fc_estate, (PLpgSQL_stmt_assert *) fc_stmt);
				break;

			case PLPGSQL_STMT_EXECSQL:
				fc_rc = fc_exec_stmt_execsql(fc_estate, (PLpgSQL_stmt_execsql *) fc_stmt);
				break;

			case PLPGSQL_STMT_DYNEXECUTE:
				fc_rc = fc_exec_stmt_dynexecute(fc_estate, (PLpgSQL_stmt_dynexecute *) fc_stmt);
				break;

			case PLPGSQL_STMT_DYNFORS:
				fc_rc = fc_exec_stmt_dynfors(fc_estate, (PLpgSQL_stmt_dynfors *) fc_stmt);
				break;

			case PLPGSQL_STMT_OPEN:
				fc_rc = fc_exec_stmt_open(fc_estate, (PLpgSQL_stmt_open *) fc_stmt);
				break;

			case PLPGSQL_STMT_FETCH:
				fc_rc = fc_exec_stmt_fetch(fc_estate, (PLpgSQL_stmt_fetch *) fc_stmt);
				break;

			case PLPGSQL_STMT_CLOSE:
				fc_rc = fc_exec_stmt_close(fc_estate, (PLpgSQL_stmt_close *) fc_stmt);
				break;

			case PLPGSQL_STMT_COMMIT:
				fc_rc = fc_exec_stmt_commit(fc_estate, (PLpgSQL_stmt_commit *) fc_stmt);
				break;

			case PLPGSQL_STMT_ROLLBACK:
				fc_rc = fc_exec_stmt_rollback(fc_estate, (PLpgSQL_stmt_rollback *) fc_stmt);
				break;

			default:
				/* 将 err_stmt 指向父级，因为这一个似乎损坏了 */
				fc_estate->err_stmt = fc_save_estmt;
				elog(ERROR, "unrecognized cmd_type: %d", fc_stmt->cmd_type);
				fc_rc = -1;		/* 保持编译器安静 */
		}

		/* 让插件知道我们已经完成执行此语句 */
		if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->stmt_end)
			((*plpgsql_plugin_ptr)->stmt_end) (fc_estate, fc_stmt);

		if (fc_rc != PLPGSQL_RC_OK)
		{
			fc_estate->err_stmt = fc_save_estmt;
			return fc_rc;
		}
	}							/* 语句循环结束 */

	fc_estate->err_stmt = fc_save_estmt;
	return PLPGSQL_RC_OK;
}


/* ----------
 * exec_stmt_assign			评估一个表达式并
 *				 将结果放入一个变量中。
 * ----------
 */
static int fc_exec_stmt_assign(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_assign *fc_stmt)
{
	Assert(fc_stmt->varno >= 0);

	fc_exec_assign_expr(fc_estate, fc_estate->datums[fc_stmt->varno], fc_stmt->expr);

	return PLPGSQL_RC_OK;
}

/* ----------
 * exec_stmt_perform		评估查询并丢弃结果（但根据是否至少返回了一行
 *						 设置 FOUND）。
 * ----------
 */
static int fc_exec_stmt_perform(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_perform *fc_stmt)
{
	PLpgSQL_expr *fc_expr = fc_stmt->expr;

	(void) fc_exec_run_select(fc_estate, fc_expr, 0, NULL);
	fc_exec_set_found(fc_estate, (fc_estate->eval_processed != 0));
	fc_exec_eval_cleanup(fc_estate);

	return PLPGSQL_RC_OK;
}

/*
 * exec_stmt_call
 *
 * 注意：这用于 CALL 和 DO 语句。
 */
static int fc_exec_stmt_call(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_call *fc_stmt)
{
	PLpgSQL_expr *fc_expr = fc_stmt->expr;
	LocalTransactionId fc_before_lxid;
	LocalTransactionId fc_after_lxid;
	ParamListInfo fc_paramLI;
	SPIExecuteOptions fc_options;
	int			fc_rc;

	/*
	 * 如果我们还没有计划，请制定一个计划。
	 */
	if (fc_expr->plan == NULL)
		fc_exec_prepare_plan(fc_estate, fc_expr, 0);

	/*
	 * CALL 或 DO 永远不能是简单的表达式。
	 */
	Assert(!fc_expr->expr_simple_expr);

	/*
	 * 还要构造一个代表与过程输出参数相关的 plpgsql 变量的 DTYPE_ROW 数据项。
	 * 然后我们可以使用 exec_move_row() 来进行赋值。
	 */
	if (fc_stmt->is_call && fc_stmt->target == NULL)
		fc_stmt->target = fc_make_callstmt_target(fc_estate, fc_expr);

	fc_paramLI = fc_setup_param_list(fc_estate, fc_expr);

	fc_before_lxid = MyProc->lxid;

	/*
	 * 如果我们有一个过程生命周期的 resowner，使用它来持有计划的引用计数。
	 * 这可以避免在被调用的过程结束当前事务时的引用计数泄漏投诉。
	 *
	 * 同时，告诉 SPI 允许非原子执行。
	 */
	memset(&fc_options, 0, sizeof(fc_options));
	fc_options.params = fc_paramLI;
	fc_options.read_only = fc_estate->readonly_func;
	fc_options.allow_nonatomic = true;
	fc_options.owner = fc_estate->procedure_resowner;

	fc_rc = SPI_execute_plan_extended(fc_expr->plan, &fc_options);

	if (fc_rc < 0)
		elog(ERROR, "SPI_execute_plan_extended failed executing query \"%s\": %s",
			 fc_expr->query, SPI_result_code_string(fc_rc));

	fc_after_lxid = MyProc->lxid;

	if (fc_before_lxid != fc_after_lxid)
	{
		/*
			 * 如果在调用后我们处于一个新事务中，我们需要构建新的简单表达式基础设施。
			 */
		fc_estate->simple_eval_estate = NULL;
		fc_estate->simple_eval_resowner = NULL;
		fc_plpgsql_create_econtext(fc_estate);
	}

	/*
	 * 检查结果行数；如果有一行，将过程的输出值赋值回相应的变量。
	 */
	if (SPI_processed == 1)
	{
		SPITupleTable *fc_tuptab = SPI_tuptable;

		if (!fc_stmt->is_call)
			elog(ERROR, "DO statement returned a row");

		fc_exec_move_row(fc_estate, fc_stmt->target, fc_tuptab->vals[0], fc_tuptab->tupdesc);
	}
	else if (SPI_processed > 1)
		elog(ERROR, "procedure call returned more than one row");

	fc_exec_eval_cleanup(fc_estate);
	SPI_freetuptable(SPI_tuptable);

	return PLPGSQL_RC_OK;
}

/*
 * 我们构造一个代表与过程输出参数相关的 plpgsql 变量的 DTYPE_ROW 数据项。
 * 然后我们可以使用 exec_move_row() 来进行赋值。
 */
static PLpgSQL_variable *
fc_make_callstmt_target(PLpgSQL_execstate *fc_estate, PLpgSQL_expr *fc_expr)
{
	CachedPlan *fc_cplan;
	PlannedStmt *fc_pstmt;
	CallStmt   *fc_stmt;
	FuncExpr   *fc_funcexpr;
	HeapTuple	fc_func_tuple;
	Oid		   *fc_argtypes;
	char	  **fc_argnames;
	char	   *fc_argmodes;
	int			fc_numargs;
	MemoryContext fc_oldcontext;
	PLpgSQL_row *fc_row;
	int			fc_nfields;
	int			fc_i;

	/* 使用 eval_mcontext 来处理此处的任何杂物 */
	fc_oldcontext = MemoryContextSwitchTo(get_eval_mcontext(fc_estate));

	/*
	 * 获取解析的 CallStmt，并查找被调用的过程。
	 * 我们使用 SPI_plan_get_cached_plan 来覆盖 expr->plan 已过时并需要更新的边缘情况。
	 */
	fc_cplan = SPI_plan_get_cached_plan(fc_expr->plan);
	if (fc_cplan == NULL || list_length(fc_cplan->stmt_list) != 1)
		elog(ERROR, "query for CALL statement is not a CallStmt");
	fc_pstmt = linitial_node(PlannedStmt, fc_cplan->stmt_list);
	fc_stmt = (CallStmt *) fc_pstmt->utilityStmt;
	if (fc_stmt == NULL || !IsA(fc_stmt, CallStmt))
		elog(ERROR, "query for CALL statement is not a CallStmt");

	fc_funcexpr = fc_stmt->funcexpr;

	fc_func_tuple = SearchSysCache1(PROCOID,
								 ObjectIdGetDatum(fc_funcexpr->funcid));
	if (!HeapTupleIsValid(fc_func_tuple))
		elog(ERROR, "cache lookup failed for function %u",
			 fc_funcexpr->funcid);

	/*
	 * 获取参数名称和模式，以便在出现问题时能够提供准确的错误信息。
	 */
	fc_numargs = get_func_arg_info(fc_func_tuple, &fc_argtypes, &fc_argnames, &fc_argmodes);

	ReleaseSysCache(fc_func_tuple);

	/*
	 * 开始构造行 Datum; 将其保存在 fn_cxt 中，以使其具有足够的
	 * 生命周期。
	 */
	MemoryContextSwitchTo(fc_estate->func->fn_cxt);

	fc_row = (PLpgSQL_row *) palloc0(sizeof(PLpgSQL_row));
	fc_row->dtype = PLPGSQL_DTYPE_ROW;
	fc_row->refname = "(unnamed row)";
	fc_row->lineno = -1;
	fc_row->varnos = (int *) palloc(fc_numargs * sizeof(int));

	MemoryContextSwitchTo(get_eval_mcontext(fc_estate));

	/*
	 * 检查过程的参数列表。每个输出参数位置应为
	 * 未装饰的 plpgsql 变量（Datum），我们可以将其插入到行
	 * Datum 中。
	 */
	fc_nfields = 0;
	for (fc_i = 0; fc_i < fc_numargs; fc_i++)
	{
		if (fc_argmodes &&
			(fc_argmodes[fc_i] == PROARGMODE_INOUT ||
			 fc_argmodes[fc_i] == PROARGMODE_OUT))
		{
			Node	   *fc_n = list_nth(fc_stmt->outargs, fc_nfields);

			if (IsA(fc_n, Param))
			{
				Param	   *fc_param = (Param *) fc_n;
				int			fc_dno;

				/* paramid 偏移 1（参见 make_datum_param()） */
				fc_dno = fc_param->paramid - 1;
				/* 现在必须检查可分配性，因为语法无法 */
				fc_exec_check_assignable(fc_estate, fc_dno);
				fc_row->varnos[fc_nfields++] = fc_dno;
			}
			else
			{
				/* 如果可用，则使用参数名称报告错误 */
				if (fc_argnames && fc_argnames[fc_i] && fc_argnames[fc_i][0])
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("procedure parameter \"%s\" is an output parameter but corresponding argument is not writable",
									fc_argnames[fc_i])));
				else
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("procedure parameter %d is an output parameter but corresponding argument is not writable",
									fc_i + 1)));
			}
		}
	}

	Assert(fc_nfields == list_length(fc_stmt->outargs));

	fc_row->nfields = fc_nfields;

	ReleaseCachedPlan(fc_cplan, CurrentResourceOwner);

	MemoryContextSwitchTo(fc_oldcontext);

	return (PLpgSQL_variable *) fc_row;
}

/* ----------
 * exec_stmt_getdiag					将内部 PG 信息放入
 *										指定的变量中。
 * ----------
 */
static int fc_exec_stmt_getdiag(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_getdiag *fc_stmt)
{
	ListCell   *fc_lc;

	/*
	 * GET STACKED DIAGNOSTICS 仅在异常处理程序内部有效。
	 *
	 * 注意：我们相信语法已经禁止了相关项目类型
	 * 如果不是 is_stacked，否则我们将会在下面宕机。
	 */
	if (fc_stmt->is_stacked && fc_estate->cur_error == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER),
				 errmsg("GET STACKED DIAGNOSTICS cannot be used outside an exception handler")));

	foreach(fc_lc, fc_stmt->diag_items)
	{
		PLpgSQL_diag_item *fc_diag_item = (PLpgSQL_diag_item *) lfirst(fc_lc);
		PLpgSQL_datum *fc_var = fc_estate->datums[fc_diag_item->target];

		switch (fc_diag_item->kind)
		{
			case PLPGSQL_GETDIAG_ROW_COUNT:
				fc_exec_assign_value(fc_estate, fc_var,
								  UInt64GetDatum(fc_estate->eval_processed),
								  false, INT8OID, -1);
				break;

			case PLPGSQL_GETDIAG_ERROR_CONTEXT:
				fc_exec_assign_c_string(fc_estate, fc_var,
									 fc_estate->cur_error->context);
				break;

			case PLPGSQL_GETDIAG_ERROR_DETAIL:
				fc_exec_assign_c_string(fc_estate, fc_var,
									 fc_estate->cur_error->detail);
				break;

			case PLPGSQL_GETDIAG_ERROR_HINT:
				fc_exec_assign_c_string(fc_estate, fc_var,
									 fc_estate->cur_error->hint);
				break;

			case PLPGSQL_GETDIAG_RETURNED_SQLSTATE:
				fc_exec_assign_c_string(fc_estate, fc_var,
									 unpack_sql_state(fc_estate->cur_error->sqlerrcode));
				break;

			case PLPGSQL_GETDIAG_COLUMN_NAME:
				fc_exec_assign_c_string(fc_estate, fc_var,
									 fc_estate->cur_error->column_name);
				break;

			case PLPGSQL_GETDIAG_CONSTRAINT_NAME:
				fc_exec_assign_c_string(fc_estate, fc_var,
									 fc_estate->cur_error->constraint_name);
				break;

			case PLPGSQL_GETDIAG_DATATYPE_NAME:
				fc_exec_assign_c_string(fc_estate, fc_var,
									 fc_estate->cur_error->datatype_name);
				break;

			case PLPGSQL_GETDIAG_MESSAGE_TEXT:
				fc_exec_assign_c_string(fc_estate, fc_var,
									 fc_estate->cur_error->message);
				break;

			case PLPGSQL_GETDIAG_TABLE_NAME:
				fc_exec_assign_c_string(fc_estate, fc_var,
									 fc_estate->cur_error->table_name);
				break;

			case PLPGSQL_GETDIAG_SCHEMA_NAME:
				fc_exec_assign_c_string(fc_estate, fc_var,
									 fc_estate->cur_error->schema_name);
				break;

			case PLPGSQL_GETDIAG_CONTEXT:
				{
					char	   *fc_contextstackstr;
					MemoryContext fc_oldcontext;

					/* 使用 eval_mcontext 处理短生命周期字符串 */
					fc_oldcontext = MemoryContextSwitchTo(get_eval_mcontext(fc_estate));
					fc_contextstackstr = GetErrorContextStack();
					MemoryContextSwitchTo(fc_oldcontext);

					fc_exec_assign_c_string(fc_estate, fc_var, fc_contextstackstr);
				}
				break;

			default:
				elog(ERROR, "unrecognized diagnostic item kind: %d",
					 fc_diag_item->kind);
		}
	}

	fc_exec_eval_cleanup(fc_estate);

	return PLPGSQL_RC_OK;
}

/* ----------
 * exec_stmt_if				评估布尔表达式并
 *				 有条件地执行真或假的主体
 * ----------
 */
static int fc_exec_stmt_if(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_if *fc_stmt)
{
	bool		fc_value;
	bool		fc_isnull;
	ListCell   *fc_lc;

	fc_value = fc_exec_eval_boolean(fc_estate, fc_stmt->cond, &fc_isnull);
	fc_exec_eval_cleanup(fc_estate);
	if (!fc_isnull && fc_value)
		return fc_exec_stmts(fc_estate, fc_stmt->then_body);

	foreach(fc_lc, fc_stmt->elsif_list)
	{
		PLpgSQL_if_elsif *fc_elif = (PLpgSQL_if_elsif *) lfirst(fc_lc);

		fc_value = fc_exec_eval_boolean(fc_estate, fc_elif->cond, &fc_isnull);
		fc_exec_eval_cleanup(fc_estate);
		if (!fc_isnull && fc_value)
			return fc_exec_stmts(fc_estate, fc_elif->stmts);
	}

	return fc_exec_stmts(fc_estate, fc_stmt->else_body);
}


/*-----------
 * exec_stmt_case
 *-----------
 */
static int fc_exec_stmt_case(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_case *fc_stmt)
{
	PLpgSQL_var *fc_t_var = NULL;
	bool		fc_isnull;
	ListCell   *fc_l;

	if (fc_stmt->t_expr != NULL)
	{
		/* 简单情况 */
		Datum		fc_t_val;
		Oid			fc_t_typoid;
		int32		fc_t_typmod;

		fc_t_val = fc_exec_eval_expr(fc_estate, fc_stmt->t_expr,
							   &fc_isnull, &fc_t_typoid, &fc_t_typmod);

		fc_t_var = (PLpgSQL_var *) fc_estate->datums[fc_stmt->t_varno];

		/*
		 * 当预期数据类型与实际数据类型不同时，进行更改。
		 * 请注意，我们在这里修改的是 Datum 的执行副本，因此
		 * 这不会影响原始存储的函数解析树。 （理论上，
		 * 如果表达式数据类型在执行过程中不断变化，
		 * 这可能导致函数生命周期内存泄漏。不过这似乎
		 * 并不值得担心。）
		 */
		if (fc_t_var->datatype->typoid != fc_t_typoid ||
			fc_t_var->datatype->atttypmod != fc_t_typmod)
			fc_t_var->datatype = plpgsql_build_datatype(fc_t_typoid,
													 fc_t_typmod,
													 fc_estate->func->fn_input_collation,
													 NULL);

		/* 现在我们可以将值赋给变量 */
		fc_exec_assign_value(fc_estate,
						  (PLpgSQL_datum *) fc_t_var,
						  fc_t_val,
						  fc_isnull,
						  fc_t_typoid,
						  fc_t_typmod);

		fc_exec_eval_cleanup(fc_estate);
	}

	/* 现在搜索成功的 WHEN 子句 */
	foreach(fc_l, fc_stmt->case_when_list)
	{
		PLpgSQL_case_when *fc_cwt = (PLpgSQL_case_when *) lfirst(fc_l);
		bool		fc_value;

		fc_value = fc_exec_eval_boolean(fc_estate, fc_cwt->expr, &fc_isnull);
		fc_exec_eval_cleanup(fc_estate);
		if (!fc_isnull && fc_value)
		{
			/* 找到了 */

			/* 我们现在可以丢弃临时变量的任何值 */
			if (fc_t_var != NULL)
				fc_assign_simple_var(fc_estate, fc_t_var, (Datum) 0, true, false);

			/* 评估语句，并完成操作 */
			return fc_exec_stmts(fc_estate, fc_cwt->stmts);
		}
	}

	/* 我们现在可以丢弃临时变量的任何值 */
	if (fc_t_var != NULL)
		fc_assign_simple_var(fc_estate, fc_t_var, (Datum) 0, true, false);

	/* SQL2003 如果没有 ELSE 子句则强制此错误 */
	if (!fc_stmt->have_else)
		ereport(ERROR,
				(errcode(ERRCODE_CASE_NOT_FOUND),
				 errmsg("case not found"),
				 errhint("CASE statement is missing ELSE part.")));

	/* 评估 ELSE 语句，并完成操作 */
	return fc_exec_stmts(fc_estate, fc_stmt->else_stmts);
}


/* ----------
 * exec_stmt_loop			循环执行语句直到
 *				 发生退出。
 * ----------
 */
static int fc_exec_stmt_loop(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_loop *fc_stmt)
{
	int			fc_rc = PLPGSQL_RC_OK;

	for (;;)
	{
		fc_rc = fc_exec_stmts(fc_estate, fc_stmt->body);

		LOOP_RC_PROCESSING(fc_stmt->label, break);
	}

	return fc_rc;
}


/* ----------
 * exec_stmt_while			只要表达式评估为
 *				 true 或发生退出，则循环执行语句。
 * ----------
 */
static int fc_exec_stmt_while(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_while *fc_stmt)
{
	int			fc_rc = PLPGSQL_RC_OK;

	for (;;)
	{
		bool		fc_value;
		bool		fc_isnull;

		fc_value = fc_exec_eval_boolean(fc_estate, fc_stmt->cond, &fc_isnull);
		fc_exec_eval_cleanup(fc_estate);

		if (fc_isnull || !fc_value)
			break;

		fc_rc = fc_exec_stmts(fc_estate, fc_stmt->body);

		LOOP_RC_PROCESSING(fc_stmt->label, break);
	}

	return fc_rc;
}


/* ----------
 * exec_stmt_fori			迭代整型变量
 *				 从下限到上限
 *				 按 BY 值递增或递减
 * ----------
 */
static int fc_exec_stmt_fori(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_fori *fc_stmt)
{
	PLpgSQL_var *fc_var;
	Datum		fc_value;
	bool		fc_isnull;
	Oid			fc_valtype;
	int32		fc_valtypmod;
	int32		fc_loop_value;
	int32		fc_end_value;
	int32		fc_step_value;
	bool		fc_found = false;
	int			fc_rc = PLPGSQL_RC_OK;

	fc_var = (PLpgSQL_var *) (fc_estate->datums[fc_stmt->var->dno]);

	/*
	 * 获取下限的值
	 */
	fc_value = fc_exec_eval_expr(fc_estate, fc_stmt->lower,
						   &fc_isnull, &fc_valtype, &fc_valtypmod);
	fc_value = fc_exec_cast_value(fc_estate, fc_value, &fc_isnull,
							fc_valtype, fc_valtypmod,
							fc_var->datatype->typoid,
							fc_var->datatype->atttypmod);
	if (fc_isnull)
		ereport(ERROR,
				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
				 errmsg("lower bound of FOR loop cannot be null")));
	fc_loop_value = DatumGetInt32(fc_value);
	fc_exec_eval_cleanup(fc_estate);

	/*
	 * 获取上限的值
	 */
	fc_value = fc_exec_eval_expr(fc_estate, fc_stmt->upper,
						   &fc_isnull, &fc_valtype, &fc_valtypmod);
	fc_value = fc_exec_cast_value(fc_estate, fc_value, &fc_isnull,
							fc_valtype, fc_valtypmod,
							fc_var->datatype->typoid,
							fc_var->datatype->atttypmod);
	if (fc_isnull)
		ereport(ERROR,
				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
				 errmsg("upper bound of FOR loop cannot be null")));
	fc_end_value = DatumGetInt32(fc_value);
	fc_exec_eval_cleanup(fc_estate);

	/*
	 * 获取步长值
	 */
	if (fc_stmt->step)
	{
		fc_value = fc_exec_eval_expr(fc_estate, fc_stmt->step,
							   &fc_isnull, &fc_valtype, &fc_valtypmod);
		fc_value = fc_exec_cast_value(fc_estate, fc_value, &fc_isnull,
								fc_valtype, fc_valtypmod,
								fc_var->datatype->typoid,
								fc_var->datatype->atttypmod);
		if (fc_isnull)
			ereport(ERROR,
					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
					 errmsg("BY value of FOR loop cannot be null")));
		fc_step_value = DatumGetInt32(fc_value);
		fc_exec_eval_cleanup(fc_estate);
		if (fc_step_value <= 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("BY value of FOR loop must be greater than zero")));
	}
	else
		fc_step_value = 1;

	/*
	 * 现在执行循环
	 */
	for (;;)
	{
		/*
		 * 检查上限
		 */
		if (fc_stmt->reverse)
		{
			if (fc_loop_value < fc_end_value)
				break;
		}
		else
		{
			if (fc_loop_value > fc_end_value)
				break;
		}

		fc_found = true;			/* 至少循环了一次 */

		/*
		 * 将当前值赋给循环变量
		 */
		fc_assign_simple_var(fc_estate, fc_var, Int32GetDatum(fc_loop_value), false, false);

		/*
		 * 执行语句
		 */
		fc_rc = fc_exec_stmts(fc_estate, fc_stmt->body);

		LOOP_RC_PROCESSING(fc_stmt->label, break);

		/*
		 * 增加/减少循环值，除非它会导致溢出，
		 * 在这种情况下退出循环。
		 */
		if (fc_stmt->reverse)
		{
			if (fc_loop_value < (PG_INT32_MIN + fc_step_value))
				break;
			fc_loop_value -= fc_step_value;
		}
		else
		{
			if (fc_loop_value > (PG_INT32_MAX - fc_step_value))
				break;
			fc_loop_value += fc_step_value;
		}
	}

	/*
	 * 设置 FOUND 变量以指示执行循环的结果
	 * （即我们是否循环了一次或多次）。这必须在这里设置，以便
	 * 不干扰循环处理内部 FOUND 变量的值。
	 */
	fc_exec_set_found(fc_estate, fc_found);

	return fc_rc;
}


/* ----------
 * exec_stmt_fors			执行查询，将每个
 *				 元组分配给记录或行，并
 *				 为它执行一组语句。
 * ----------
 */
static int fc_exec_stmt_fors(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_fors *fc_stmt)
{
	Portal		fc_portal;
	int			fc_rc;

	/*
	 * 使用 exec_run_select 为语句打开隐式游标
	 */
	fc_exec_run_select(fc_estate, fc_stmt->query, 0, &fc_portal);

	/*
	 * 执行循环
	 */
	fc_rc = fc_exec_for_query(fc_estate, (PLpgSQL_stmt_forq *) fc_stmt, fc_portal, true);

	/*
	 * 关闭隐式游标
	 */
	SPI_cursor_close(fc_portal);

	return fc_rc;
}


/* ----------
 * exec_stmt_forc			为游标中的每一行执行循环。
 * ----------
 */
static int fc_exec_stmt_forc(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_forc *fc_stmt)
{
	PLpgSQL_var *fc_curvar;
	MemoryContext fc_stmt_mcontext = NULL;
	char	   *fc_curname = NULL;
	PLpgSQL_expr *fc_query;
	ParamListInfo fc_paramLI;
	Portal		fc_portal;
	int			fc_rc;

	/* ----------
	 * 获取游标变量，如果它有分配的名称，检查
	 * 它是否当前未在使用。
	 * ----------
	 */
	fc_curvar = (PLpgSQL_var *) (fc_estate->datums[fc_stmt->curvar]);
	if (!fc_curvar->isnull)
	{
		MemoryContext fc_oldcontext;

		/* 我们只需要 stmt_mcontext 来保存游标名称字符串 */
		fc_stmt_mcontext = fc_get_stmt_mcontext(fc_estate);
		fc_oldcontext = MemoryContextSwitchTo(fc_stmt_mcontext);
		fc_curname = TextDatumGetCString(fc_curvar->value);
		MemoryContextSwitchTo(fc_oldcontext);

		if (SPI_cursor_find(fc_curname) != NULL)
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_CURSOR),
					 errmsg("cursor \"%s\" already in use", fc_curname)));
	}

	/* ----------
	 * 像 OPEN 命令一样打开游标
	 *
	 * 注意：解析器应该已经检查了语句是否提供了
	 * 参数，如果游标需要它们，但我们再检查一次以确保安全。
	 * ----------
	 */
	if (fc_stmt->argquery != NULL)
	{
		/* ----------
		 * 使用参数 OPEN CURSOR。我们伪造一个 SELECT ... INTO ...
		 * 语句来评估参数并将它们放入
		 * 内部行中。
		 * ----------
		 */
		PLpgSQL_stmt_execsql fc_set_args;

		if (fc_curvar->cursor_explicit_argrow < 0)
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("arguments given for cursor without arguments")));

		memset(&fc_set_args, 0, sizeof(fc_set_args));
		fc_set_args.cmd_type = PLPGSQL_STMT_EXECSQL;
		fc_set_args.lineno = fc_stmt->lineno;
		fc_set_args.sqlstmt = fc_stmt->argquery;
		fc_set_args.into = true;
		/* XXX 历史上这并不是严格的 */
		fc_set_args.target = (PLpgSQL_variable *)
			(fc_estate->datums[fc_curvar->cursor_explicit_argrow]);

		if (fc_exec_stmt_execsql(fc_estate, &fc_set_args) != PLPGSQL_RC_OK)
			elog(ERROR, "open cursor failed during argument processing");
	}
	else
	{
		if (fc_curvar->cursor_explicit_argrow >= 0)
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("arguments required for cursor")));
	}

	fc_query = fc_curvar->cursor_explicit_expr;
	Assert(fc_query);

	if (fc_query->plan == NULL)
		fc_exec_prepare_plan(fc_estate, fc_query, fc_curvar->cursor_options);

	/*
	 * 为这个查询设置 ParamListInfo
	 */
	fc_paramLI = fc_setup_param_list(fc_estate, fc_query);

	/*
	 * 打开游标（参数列表将被复制到门户中）
	 */
	fc_portal = SPI_cursor_open_with_paramlist(fc_curname, fc_query->plan,
											fc_paramLI,
											fc_estate->readonly_func);
	if (fc_portal == NULL)
		elog(ERROR, "could not open cursor: %s",
			 SPI_result_code_string(SPI_result));

	/*
	 * 如果游标变量为 NULL，将生成的门户名称存储其中，
	 * 在验证可以赋值后进行。
	 */
	if (fc_curname == NULL)
	{
		fc_exec_check_assignable(fc_estate, fc_stmt->curvar);
		fc_assign_text_var(fc_estate, fc_curvar, fc_portal->name);
	}

	/*
	 * 在进入 exec_for_query 之前进行清理
	 */
	fc_exec_eval_cleanup(fc_estate);
	if (fc_stmt_mcontext)
		MemoryContextReset(fc_stmt_mcontext);

	/*
	 * 执行循环。我们不能预取，因为游标对用户可访问，
	 * 比如在循环中通过 UPDATE WHERE CURRENT OF。
	 */
	fc_rc = fc_exec_for_query(fc_estate, (PLpgSQL_stmt_forq *) fc_stmt, fc_portal, false);

	/* ----------
	 * 关闭门户，如果游标变量最初为 NULL，则恢复游标变量。
	 * ----------
	 */
	SPI_cursor_close(fc_portal);

	if (fc_curname == NULL)
		fc_assign_simple_var(fc_estate, fc_curvar, (Datum) 0, true, false);

	return fc_rc;
}


/* ----------
 * exec_stmt_foreach_a			遍历数组的元素或切片
 *
 * 当遍历元素时，循环变量与数组存储的类型相同
 * （例如：整数），当遍历切片时，循环变量
 * 是一个大小和维度与切片大小匹配的数组。
 * ----------
 */
static int fc_exec_stmt_foreach_a(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_foreach_a *fc_stmt)
{
	ArrayType  *fc_arr;
	Oid			fc_arrtype;
	int32		fc_arrtypmod;
	PLpgSQL_datum *fc_loop_var;
	Oid			fc_loop_var_elem_type;
	bool		fc_found = false;
	int			fc_rc = PLPGSQL_RC_OK;
	MemoryContext fc_stmt_mcontext;
	MemoryContext fc_oldcontext;
	ArrayIterator fc_array_iterator;
	Oid			fc_iterator_result_type;
	int32		fc_iterator_result_typmod;
	Datum		fc_value;
	bool		fc_isnull;

	/* 获取数组表达式的值 */
	fc_value = fc_exec_eval_expr(fc_estate, fc_stmt->expr, &fc_isnull, &fc_arrtype, &fc_arrtypmod);
	if (fc_isnull)
		ereport(ERROR,
				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
				 errmsg("FOREACH expression must not be null")));

	/*
	 * 尽可能在 stmt_mcontext 中执行下面的代码，以避免从被调用的子例程中泄漏。
	 * 我们需要一个私有的 stmt_mcontext，因为
	 * 我们将调用任意的语句代码。
	 */
	fc_stmt_mcontext = fc_get_stmt_mcontext(fc_estate);
	fc_push_stmt_mcontext(fc_estate);
	fc_oldcontext = MemoryContextSwitchTo(fc_stmt_mcontext);

	/* 检查表达式的类型 - 必须是数组 */
	if (!OidIsValid(get_element_type(fc_arrtype)))
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("FOREACH expression must yield an array, not type %s",
						format_type_be(fc_arrtype))));

	/*
	 * 我们必须将数组复制到 stmt_mcontext，否则它将在
	 * exec_eval_cleanup 中消失。这很烦人，但清理肯定会在
	 * 执行循环体时发生，所以我们别无选择。
	 */
	fc_arr = DatumGetArrayTypePCopy(fc_value);

	/* 清理任何剩余的临时内存 */
	fc_exec_eval_cleanup(fc_estate);

	/* 切片维度必须小于或等于数组维度 */
	if (fc_stmt->slice < 0 || fc_stmt->slice > ARR_NDIM(fc_arr))
		ereport(ERROR,
				(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
				 errmsg("slice dimension (%d) is out of the valid range 0..%d",
						fc_stmt->slice, ARR_NDIM(fc_arr))));

	/* 设置循环变量并查看它是否是数组类型 */
	fc_loop_var = fc_estate->datums[fc_stmt->varno];
	if (fc_loop_var->dtype == PLPGSQL_DTYPE_REC ||
		fc_loop_var->dtype == PLPGSQL_DTYPE_ROW)
	{
		/*
		 * 记录/行变量肯定不是数组类型，并且可能还没有
		 * 完全初始化，因此不要尝试获取它的类型
		 */
		fc_loop_var_elem_type = InvalidOid;
	}
	else
		fc_loop_var_elem_type = get_element_type(plpgsql_exec_get_datum_type(fc_estate,
																		  fc_loop_var));

	/*
	 * 对循环变量类型进行基本检查。我们在这里不会太过努力，也
	 * 不应太挑剔，因为exec_assign_value可以强制不同类型的值。
	 * 但如果循环变量的数组性不正确，抱怨一下似乎是值得的。
	 */
	if (fc_stmt->slice > 0 && fc_loop_var_elem_type == InvalidOid)
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("FOREACH ... SLICE loop variable must be of an array type")));
	if (fc_stmt->slice == 0 && fc_loop_var_elem_type != InvalidOid)
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("FOREACH loop variable must not be of an array type")));

	/* 创建一个迭代器以遍历数组 */
	fc_array_iterator = array_create_iterator(fc_arr, fc_stmt->slice, NULL);

	/* 确定迭代器结果类型 */
	if (fc_stmt->slice > 0)
	{
		/* 切片时，结果的名义类型与数组类型相同 */
		fc_iterator_result_type = fc_arrtype;
		fc_iterator_result_typmod = fc_arrtypmod;
	}
	else
	{
		/* 未切片时，结果是单独的数组元素 */
		fc_iterator_result_type = ARR_ELEMTYPE(fc_arr);
		fc_iterator_result_typmod = fc_arrtypmod;
	}

	/* 遍历数组元素或切片 */
	while (array_iterate(fc_array_iterator, &fc_value, &fc_isnull))
	{
		fc_found = true;			/* 至少循环了一次 */

		/* exec_assign_value和exec_stmts必须在主上下文中运行 */
		MemoryContextSwitchTo(fc_oldcontext);

		/* 将当前元素/切片分配给循环变量 */
		fc_exec_assign_value(fc_estate, fc_loop_var, fc_value, fc_isnull,
						  fc_iterator_result_type, fc_iterator_result_typmod);

		/* 在切片情况下，值是临时的；必须释放它以避免泄漏 */
		if (fc_stmt->slice > 0)
			pfree(DatumGetPointer(fc_value));

		/*
		 * 执行语句
		 */
		fc_rc = fc_exec_stmts(fc_estate, fc_stmt->body);

		LOOP_RC_PROCESSING(fc_stmt->label, break);

		MemoryContextSwitchTo(fc_stmt_mcontext);
	}

	/* 恢复内存上下文状态 */
	MemoryContextSwitchTo(fc_oldcontext);
	fc_pop_stmt_mcontext(fc_estate);

	/* 释放临时内存，包括数组值 */
	MemoryContextReset(fc_stmt_mcontext);

	/*
	 * 设置 FOUND 变量以指示执行循环的结果
	 * （即我们是否循环了一次或多次）。这必须在这里设置，以便
	 * 不干扰循环处理内部 FOUND 变量的值。
	 */
	fc_exec_set_found(fc_estate, fc_found);

	return fc_rc;
}


/* ----------
 * exec_stmt_exit			实现EXIT和CONTINUE
 *
 * 这开始退出/重新启动循环的过程。
 * ----------
 */
static int fc_exec_stmt_exit(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_exit *fc_stmt)
{
	/*
	 * 如果退出/继续有条件，评估它
	 */
	if (fc_stmt->cond != NULL)
	{
		bool		fc_value;
		bool		fc_isnull;

		fc_value = fc_exec_eval_boolean(fc_estate, fc_stmt->cond, &fc_isnull);
		fc_exec_eval_cleanup(fc_estate);
		if (fc_isnull || fc_value == false)
			return PLPGSQL_RC_OK;
	}

	fc_estate->exitlabel = fc_stmt->label;
	if (fc_stmt->is_exit)
		return PLPGSQL_RC_EXIT;
	else
		return PLPGSQL_RC_CONTINUE;
}


/* ----------
 * exec_stmt_return			评估表达式并开始
 *					从函数返回。
 *
 * 注意：结果可能在eval_mcontext中。因此，我们在
 * 解开控制栈时不能执行exec_eval_cleanup。
 * ----------
 */
static int fc_exec_stmt_return(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_return *fc_stmt)
{
	/*
	 * 如果正在处理一个返回集合的PL/pgSQL函数，最后的RETURN
	 * 表示该函数已经完成产生元组。其余的工作将在顶层完成。
	 */
	if (fc_estate->retisset)
		return PLPGSQL_RC_RETURN;

	/* 为空结果初始化 */
	fc_estate->retval = (Datum) 0;
	fc_estate->retisnull = true;
	fc_estate->rettype = InvalidOid;

	/*
	 * 特殊情况下当RETURN表达式是简单的变量引用；
	 * 特别是，这一路径在具有一个或多个OUT参数的函数中始终被采用。
	 *
	 * 这个特殊情况对于返回具有R/W扩展值的变量特别有效：
	 * 我们可以将R/W指针直接放入estate->retval中，从而以低廉的
	 * 成本将值转移到调用者的上下文。如果通过exec_eval_expr，
	 * 我们最后会得到一个R/O指针。这里跳过MakeExpandedObjectReadOnly
	 * 是可以的，因为我们知道在函数内部不再需要变量的值。
	 */
	if (fc_stmt->retvarno >= 0)
	{
		PLpgSQL_datum *fc_retvar = fc_estate->datums[fc_stmt->retvarno];

		switch (fc_retvar->dtype)
		{
			case PLPGSQL_DTYPE_PROMISE:
				/* 如有需要，则履行承诺，然后像普通变量一样处理 */
				fc_plpgsql_fulfill_promise(fc_estate, (PLpgSQL_var *) fc_retvar);

				/* FALL THRU */

			case PLPGSQL_DTYPE_VAR:
				{
					PLpgSQL_var *fc_var = (PLpgSQL_var *) fc_retvar;

					fc_estate->retval = fc_var->value;
					fc_estate->retisnull = fc_var->isnull;
					fc_estate->rettype = fc_var->datatype->typoid;

					/*
					 * PLpgSQL_var 不能是复合类型，因此
					 * 如果 retistuple，转换必须失败。我们抛出一个自定义
					 * 错误主要是为了与历史行为的一致性。
					 * 出于同样的原因，如果结果
					 * 为 NULL，我们不抛出错误。(注意，plpgsql_exec_trigger 假设
					 * 任何非空结果都已验证为复合类型。)
					 */
					if (fc_estate->retistuple && !fc_estate->retisnull)
						ereport(ERROR,
								(errcode(ERRCODE_DATATYPE_MISMATCH),
								 errmsg("cannot return non-composite value from function returning composite type")));
				}
				break;

			case PLPGSQL_DTYPE_REC:
				{
					PLpgSQL_rec *fc_rec = (PLpgSQL_rec *) fc_retvar;

					/* 如果记录为空，我们返回 NULL，而不是一行空值 */
					if (fc_rec->erh && !ExpandedRecordIsEmpty(fc_rec->erh))
					{
						fc_estate->retval = ExpandedRecordGetDatum(fc_rec->erh);
						fc_estate->retisnull = false;
						fc_estate->rettype = fc_rec->rectypeid;
					}
				}
				break;

			case PLPGSQL_DTYPE_ROW:
				{
					PLpgSQL_row *fc_row = (PLpgSQL_row *) fc_retvar;
					int32		fc_rettypmod;

					/* 如果有多个 OUT 参数，我们会到达这里 */
					fc_exec_eval_datum(fc_estate,
									(PLpgSQL_datum *) fc_row,
									&fc_estate->rettype,
									&fc_rettypmod,
									&fc_estate->retval,
									&fc_estate->retisnull);
				}
				break;

			default:
				elog(ERROR, "unrecognized dtype: %d", fc_retvar->dtype);
		}

		return PLPGSQL_RC_RETURN;
	}

	if (fc_stmt->expr != NULL)
	{
		int32		fc_rettypmod;

		fc_estate->retval = fc_exec_eval_expr(fc_estate, fc_stmt->expr,
										&(fc_estate->retisnull),
										&(fc_estate->rettype),
										&fc_rettypmod);

		/*
		 * 与上面的 DTYPE_VAR 情况一样，如果在返回元组的函数中返回
		 * 非空、非复合值，则抛出自定义错误。
		 */
		if (fc_estate->retistuple && !fc_estate->retisnull &&
			!type_is_rowtype(fc_estate->rettype))
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("cannot return non-composite value from function returning composite type")));

		return PLPGSQL_RC_RETURN;
	}

	/*
	 * 处理返回 VOID 的函数的特殊情况：返回
	 * 一个非空的 VOID 值而不是 NULL。这虽然重要性有待商榷，但为了
	 * 向后兼容而保留。但我们不这样做于过程。
	 */
	if (fc_estate->fn_rettype == VOIDOID &&
		fc_estate->func->fn_prokind != PROKIND_PROCEDURE)
	{
		fc_estate->retval = (Datum) 0;
		fc_estate->retisnull = false;
		fc_estate->rettype = VOIDOID;
	}

	return PLPGSQL_RC_RETURN;
}

/* ----------
 * exec_stmt_return_next		评估一个表达式并将其添加到
 *								当前 SRF 返回的元组列表中。
 * ----------
 */
static int fc_exec_stmt_return_next(PLpgSQL_execstate *fc_estate,
					  PLpgSQL_stmt_return_next *fc_stmt)
{
	TupleDesc	fc_tupdesc;
	int			fc_natts;
	HeapTuple	fc_tuple;
	MemoryContext fc_oldcontext;

	if (!fc_estate->retisset)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("cannot use RETURN NEXT in a non-SETOF function")));

	if (fc_estate->tuple_store == NULL)
		fc_exec_init_tuple_store(fc_estate);

	/* tuple_store_desc 将由 exec_init_tuple_store 填充 */
	fc_tupdesc = fc_estate->tuple_store_desc;
	fc_natts = fc_tupdesc->natts;

	/*
	 * 当 RETURN NEXT 表达式是一个简单变量
	 * 引用时的特殊路径；特别是，在具有一个或多个 OUT 参数的
	 * 函数中始终采取此路径。
	 *
	 * 与 exec_stmt_return 不同，这里没有特别的好处，适用于 R/W 扩展
	 * 值，因为它们必须被扁平化以放入元组存储。
	 * 实际上，我们最好将它们设为 R/O，以避免类型转换步骤
	 * 可能就地更改它们的风险。
	 */
	if (fc_stmt->retvarno >= 0)
	{
		PLpgSQL_datum *fc_retvar = fc_estate->datums[fc_stmt->retvarno];

		switch (fc_retvar->dtype)
		{
			case PLPGSQL_DTYPE_PROMISE:
				/* 如有需要，则履行承诺，然后像普通变量一样处理 */
				fc_plpgsql_fulfill_promise(fc_estate, (PLpgSQL_var *) fc_retvar);

				/* FALL THRU */

			case PLPGSQL_DTYPE_VAR:
				{
					PLpgSQL_var *fc_var = (PLpgSQL_var *) fc_retvar;
					Datum		fc_retval = fc_var->value;
					bool		fc_isNull = fc_var->isnull;
					Form_pg_attribute fc_attr = TupleDescAttr(fc_tupdesc, 0);

					if (fc_natts != 1)
						ereport(ERROR,
								(errcode(ERRCODE_DATATYPE_MISMATCH),
								 errmsg("wrong result type supplied in RETURN NEXT")));

					/* 关于类型转换步骤我们要非常小心 */
					fc_retval = MakeExpandedObjectReadOnly(fc_retval,
														fc_isNull,
														fc_var->datatype->typlen);

					/* 如有必要，强制转换类型 */
					fc_retval = fc_exec_cast_value(fc_estate,
											 fc_retval,
											 &fc_isNull,
											 fc_var->datatype->typoid,
											 fc_var->datatype->atttypmod,
											 fc_attr->atttypid,
											 fc_attr->atttypmod);

					tuplestore_putvalues(fc_estate->tuple_store, fc_tupdesc,
										 &fc_retval, &fc_isNull);
				}
				break;

			case PLPGSQL_DTYPE_REC:
				{
					PLpgSQL_rec *fc_rec = (PLpgSQL_rec *) fc_retvar;
					TupleDesc	fc_rec_tupdesc;
					TupleConversionMap *fc_tupmap;

					/* 如果 rec 为 null，则尝试将其转换为一行空值 */
					if (fc_rec->erh == NULL)
						fc_instantiate_empty_record_variable(fc_estate, fc_rec);
					if (ExpandedRecordIsEmpty(fc_rec->erh))
						deconstruct_expanded_record(fc_rec->erh);

					/* 使用 eval_mcontext 进行元组转换工作 */
					fc_oldcontext = MemoryContextSwitchTo(get_eval_mcontext(fc_estate));
					fc_rec_tupdesc = expanded_record_get_tupdesc(fc_rec->erh);
					fc_tupmap = convert_tuples_by_position(fc_rec_tupdesc,
														fc_tupdesc,
														gettext_noop("wrong record type supplied in RETURN NEXT"));
					fc_tuple = expanded_record_get_tuple(fc_rec->erh);
					if (fc_tupmap)
						fc_tuple = execute_attr_map_tuple(fc_tuple, fc_tupmap);
					tuplestore_puttuple(fc_estate->tuple_store, fc_tuple);
					MemoryContextSwitchTo(fc_oldcontext);
				}
				break;

			case PLPGSQL_DTYPE_ROW:
				{
					PLpgSQL_row *fc_row = (PLpgSQL_row *) fc_retvar;

					/* 如果有多个 OUT 参数，我们会到达这里 */

					/* 使用 eval_mcontext 进行元组转换工作 */
					fc_oldcontext = MemoryContextSwitchTo(get_eval_mcontext(fc_estate));
					fc_tuple = fc_make_tuple_from_row(fc_estate, fc_row, fc_tupdesc);
					if (fc_tuple == NULL)	/* 不应该发生 */
						ereport(ERROR,
								(errcode(ERRCODE_DATATYPE_MISMATCH),
								 errmsg("wrong record type supplied in RETURN NEXT")));
					tuplestore_puttuple(fc_estate->tuple_store, fc_tuple);
					MemoryContextSwitchTo(fc_oldcontext);
				}
				break;

			default:
				elog(ERROR, "unrecognized dtype: %d", fc_retvar->dtype);
				break;
		}
	}
	else if (fc_stmt->expr)
	{
		Datum		fc_retval;
		bool		fc_isNull;
		Oid			fc_rettype;
		int32		fc_rettypmod;

		fc_retval = fc_exec_eval_expr(fc_estate,
								fc_stmt->expr,
								&fc_isNull,
								&fc_rettype,
								&fc_rettypmod);

		if (fc_estate->retistuple)
		{
			/* 表达式应该是 RECORD 或复合类型 */
			if (!fc_isNull)
			{
				HeapTupleData fc_tmptup;
				TupleDesc	fc_retvaldesc;
				TupleConversionMap *fc_tupmap;

				if (!type_is_rowtype(fc_rettype))
					ereport(ERROR,
							(errcode(ERRCODE_DATATYPE_MISMATCH),
							 errmsg("cannot return non-composite value from function returning composite type")));

				/* 使用 eval_mcontext 进行元组转换工作 */
				fc_oldcontext = MemoryContextSwitchTo(get_eval_mcontext(fc_estate));
				fc_retvaldesc = fc_deconstruct_composite_datum(fc_retval, &fc_tmptup);
				fc_tuple = &fc_tmptup;
				fc_tupmap = convert_tuples_by_position(fc_retvaldesc, fc_tupdesc,
													gettext_noop("returned record type does not match expected record type"));
				if (fc_tupmap)
					fc_tuple = execute_attr_map_tuple(fc_tuple, fc_tupmap);
				tuplestore_puttuple(fc_estate->tuple_store, fc_tuple);
				ReleaseTupleDesc(fc_retvaldesc);
				MemoryContextSwitchTo(fc_oldcontext);
			}
			else
			{
				/* 复合 NULL --- 存储一行空值 */
				Datum	   *fc_nulldatums;
				bool	   *fc_nullflags;

				fc_nulldatums = (Datum *)
					eval_mcontext_alloc0(fc_estate, fc_natts * sizeof(Datum));
				fc_nullflags = (bool *)
					eval_mcontext_alloc(fc_estate, fc_natts * sizeof(bool));
				memset(fc_nullflags, true, fc_natts * sizeof(bool));
				tuplestore_putvalues(fc_estate->tuple_store, fc_tupdesc,
									 fc_nulldatums, fc_nullflags);
			}
		}
		else
		{
			Form_pg_attribute fc_attr = TupleDescAttr(fc_tupdesc, 0);

			/* 简单的标量结果 */
			if (fc_natts != 1)
				ereport(ERROR,
						(errcode(ERRCODE_DATATYPE_MISMATCH),
						 errmsg("wrong result type supplied in RETURN NEXT")));

			/* 如有必要，强制转换类型 */
			fc_retval = fc_exec_cast_value(fc_estate,
									 fc_retval,
									 &fc_isNull,
									 fc_rettype,
									 fc_rettypmod,
									 fc_attr->atttypid,
									 fc_attr->atttypmod);

			tuplestore_putvalues(fc_estate->tuple_store, fc_tupdesc,
								 &fc_retval, &fc_isNull);
		}
	}
	else
	{
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("RETURN NEXT must have a parameter")));
	}

	fc_exec_eval_cleanup(fc_estate);

	return PLPGSQL_RC_OK;
}

/* ----------
 * exec_stmt_return_query		评估一个查询并将其添加到
 *								当前 SRF 返回的元组列表中。
 * ----------
 */
static int fc_exec_stmt_return_query(PLpgSQL_execstate *fc_estate,
					   PLpgSQL_stmt_return_query *fc_stmt)
{
	int64		fc_tcount;
	DestReceiver *fc_treceiver;
	int			fc_rc;
	uint64		fc_processed;
	MemoryContext fc_stmt_mcontext = fc_get_stmt_mcontext(fc_estate);
	MemoryContext fc_oldcontext;

	if (!fc_estate->retisset)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("cannot use RETURN QUERY in a non-SETOF function")));

	if (fc_estate->tuple_store == NULL)
		fc_exec_init_tuple_store(fc_estate);
	/* tuplestore 中可能已经存在一些元组 */
	fc_tcount = tuplestore_tuple_count(fc_estate->tuple_store);

	/*
	 * 设置 DestReceiver 以直接将结果传输到元组存储，
	 * 如果必要的话转换行类型。DestReceiver 位于 mcontext 中。
	 */
	fc_oldcontext = MemoryContextSwitchTo(fc_stmt_mcontext);
	fc_treceiver = CreateDestReceiver(DestTuplestore);
	SetTuplestoreDestReceiverParams(fc_treceiver,
									fc_estate->tuple_store,
									fc_estate->tuple_store_cxt,
									false,
									fc_estate->tuple_store_desc,
									gettext_noop("structure of query does not match function result type"));
	MemoryContextSwitchTo(fc_oldcontext);

	if (fc_stmt->query != NULL)
	{
		/* 静态查询 */
		PLpgSQL_expr *fc_expr = fc_stmt->query;
		ParamListInfo fc_paramLI;
		SPIExecuteOptions fc_options;

		/*
		 * 在第一次调用此表达式时生成计划。
		 */
		if (fc_expr->plan == NULL)
			fc_exec_prepare_plan(fc_estate, fc_expr, CURSOR_OPT_PARALLEL_OK);

		/*
		 * 设置 ParamListInfo 传递给执行器
		 */
		fc_paramLI = fc_setup_param_list(fc_estate, fc_expr);

		/*
		 * 执行查询
		 */
		memset(&fc_options, 0, sizeof(fc_options));
		fc_options.params = fc_paramLI;
		fc_options.read_only = fc_estate->readonly_func;
		fc_options.must_return_tuples = true;
		fc_options.dest = fc_treceiver;

		fc_rc = SPI_execute_plan_extended(fc_expr->plan, &fc_options);
		if (fc_rc < 0)
			elog(ERROR, "SPI_execute_plan_extended failed executing query \"%s\": %s",
				 fc_expr->query, SPI_result_code_string(fc_rc));
	}
	else
	{
		/* RETURN QUERY EXECUTE */
		Datum		fc_query;
		bool		fc_isnull;
		Oid			fc_restype;
		int32		fc_restypmod;
		char	   *fc_querystr;
		SPIExecuteOptions fc_options;

		/*
		 * 评估 EXECUTE 关键字后的字符串表达式。其
		 * 结果是我们要执行的查询字符串。
		 */
		Assert(fc_stmt->dynquery != NULL);
		fc_query = fc_exec_eval_expr(fc_estate, fc_stmt->dynquery,
							   &fc_isnull, &fc_restype, &fc_restypmod);
		if (fc_isnull)
			ereport(ERROR,
					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
					 errmsg("query string argument of EXECUTE is null")));

		/* 获取 C 字符串表示 */
		fc_querystr = fc_convert_value_to_string(fc_estate, fc_query, fc_restype);

		/* 在清理之前将其复制到 stmt_mcontext */
		fc_querystr = MemoryContextStrdup(fc_stmt_mcontext, fc_querystr);

		fc_exec_eval_cleanup(fc_estate);

		/* 执行查询，如有必要传递参数 */
		memset(&fc_options, 0, sizeof(fc_options));
		fc_options.params = fc_exec_eval_using_params(fc_estate,
												fc_stmt->params);
		fc_options.read_only = fc_estate->readonly_func;
		fc_options.must_return_tuples = true;
		fc_options.dest = fc_treceiver;

		fc_rc = SPI_execute_extended(fc_querystr, &fc_options);
		if (fc_rc < 0)
			elog(ERROR, "SPI_execute_extended failed executing query \"%s\": %s",
				 fc_querystr, SPI_result_code_string(fc_rc));
	}

	/* 清理 */
	fc_treceiver->rDestroy(fc_treceiver);
	fc_exec_eval_cleanup(fc_estate);
	MemoryContextReset(fc_stmt_mcontext);

	/* 统计我们得到了多少元组 */
	fc_processed = tuplestore_tuple_count(fc_estate->tuple_store) - fc_tcount;

	fc_estate->eval_processed = fc_processed;
	fc_exec_set_found(fc_estate, fc_processed != 0);

	return PLPGSQL_RC_OK;
}

static void fc_exec_init_tuple_store(PLpgSQL_execstate *fc_estate)
{
	ReturnSetInfo *fc_rsi = fc_estate->rsi;
	MemoryContext fc_oldcxt;
	ResourceOwner fc_oldowner;

	/*
	 * 检查调用者是否能以我们想要的方式处理集合结果
	 */
	if (!fc_rsi || !IsA(fc_rsi, ReturnSetInfo))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("set-valued function called in context that cannot accept a set")));

	if (!(fc_rsi->allowedModes & SFRM_Materialize) ||
		fc_rsi->expectedDesc == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("materialize mode required, but it is not allowed in this context")));

	/*
	 * 切换到正确的内存上下文和资源拥有者，以存储
	 * 返回集合的 tuplestore。如果我们在为异常块打开的
	 * 子事务内，例如，仍然必须在进入此函数时活动的
	 * 资源拥有者中创建 tuplestore，而不是在子事务资源拥有者中。
	 */
	fc_oldcxt = MemoryContextSwitchTo(fc_estate->tuple_store_cxt);
	fc_oldowner = CurrentResourceOwner;
	CurrentResourceOwner = fc_estate->tuple_store_owner;

	fc_estate->tuple_store =
		tuplestore_begin_heap(fc_rsi->allowedModes & SFRM_Materialize_Random,
							  false, work_mem);

	CurrentResourceOwner = fc_oldowner;
	MemoryContextSwitchTo(fc_oldcxt);

	fc_estate->tuple_store_desc = fc_rsi->expectedDesc;
}

#define SET_RAISE_OPTION_TEXT(opt, name) \
do { \
	if (opt) \
		ereport(ERROR, \
				(errcode(ERRCODE_SYNTAX_ERROR), \
				 errmsg("RAISE option already specified: %s", \
						name))); \
	opt = MemoryContextStrdup(fc_stmt_mcontext, fc_extval); \
} while (0)

/* ----------
 * exec_stmt_raise			构建消息并通过 elog() 抛出
 * ----------
 */
static int fc_exec_stmt_raise(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_raise *fc_stmt)
{
	int			fc_err_code = 0;
	char	   *fc_condname = NULL;
	char	   *fc_err_message = NULL;
	char	   *fc_err_detail = NULL;
	char	   *fc_err_hint = NULL;
	char	   *fc_err_column = NULL;
	char	   *fc_err_constraint = NULL;
	char	   *fc_err_datatype = NULL;
	char	   *fc_err_table = NULL;
	char	   *fc_err_schema = NULL;
	MemoryContext fc_stmt_mcontext;
	ListCell   *fc_lc;

	/* 不带参数的 RAISE：重新抛出当前异常 */
	if (fc_stmt->condname == NULL && fc_stmt->message == NULL &&
		fc_stmt->options == NIL)
	{
		if (fc_estate->cur_error != NULL)
			ReThrowError(fc_estate->cur_error);
		/* 哎呀，我们不在处理程序内部 */
		ereport(ERROR,
				(errcode(ERRCODE_STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER),
				 errmsg("RAISE without parameters cannot be used outside an exception handler")));
	}

	/* 我们需要在 stmt_mcontext 中累积各种字符串 */
	fc_stmt_mcontext = fc_get_stmt_mcontext(fc_estate);

	if (fc_stmt->condname)
	{
		fc_err_code = plpgsql_recognize_err_condition(fc_stmt->condname, true);
		fc_condname = MemoryContextStrdup(fc_stmt_mcontext, fc_stmt->condname);
	}

	if (fc_stmt->message)
	{
		StringInfoData fc_ds;
		ListCell   *fc_current_param;
		char	   *fc_cp;
		MemoryContext fc_oldcontext;

		/* 在 stmt_mcontext 中构建字符串 */
		fc_oldcontext = MemoryContextSwitchTo(fc_stmt_mcontext);
		initStringInfo(&fc_ds);
		MemoryContextSwitchTo(fc_oldcontext);

		fc_current_param = list_head(fc_stmt->params);

		for (fc_cp = fc_stmt->message; *fc_cp; fc_cp++)
		{
			/*
			 * 单个 % 的出现将被下一个参数的外部表示替换。
			 * 双 %% 被转换为一个 %。
			 */
			if (fc_cp[0] == '%')
			{
				Oid			fc_paramtypeid;
				int32		fc_paramtypmod;
				Datum		fc_paramvalue;
				bool		fc_paramisnull;
				char	   *fc_extval;

				if (fc_cp[1] == '%')
				{
					appendStringInfoChar(&fc_ds, '%');
					fc_cp++;
					continue;
				}

				/* 应该在编译时检查 */
				if (fc_current_param == NULL)
					elog(ERROR, "unexpected RAISE parameter list length");

				fc_paramvalue = fc_exec_eval_expr(fc_estate,
											(PLpgSQL_expr *) lfirst(fc_current_param),
											&fc_paramisnull,
											&fc_paramtypeid,
											&fc_paramtypmod);

				if (fc_paramisnull)
					fc_extval = "<NULL>";
				else
					fc_extval = fc_convert_value_to_string(fc_estate,
													 fc_paramvalue,
													 fc_paramtypeid);
				appendStringInfoString(&fc_ds, fc_extval);
				fc_current_param = lnext(fc_stmt->params, fc_current_param);
				fc_exec_eval_cleanup(fc_estate);
			}
			else
				appendStringInfoChar(&fc_ds, fc_cp[0]);
		}

		/* 应该在编译时检查 */
		if (fc_current_param != NULL)
			elog(ERROR, "unexpected RAISE parameter list length");

		fc_err_message = fc_ds.data;
	}

	foreach(fc_lc, fc_stmt->options)
	{
		PLpgSQL_raise_option *fc_opt = (PLpgSQL_raise_option *) lfirst(fc_lc);
		Datum		fc_optionvalue;
		bool		fc_optionisnull;
		Oid			fc_optiontypeid;
		int32		fc_optiontypmod;
		char	   *fc_extval;

		fc_optionvalue = fc_exec_eval_expr(fc_estate, fc_opt->expr,
									 &fc_optionisnull,
									 &fc_optiontypeid,
									 &fc_optiontypmod);
		if (fc_optionisnull)
			ereport(ERROR,
					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
					 errmsg("RAISE statement option cannot be null")));

		fc_extval = fc_convert_value_to_string(fc_estate, fc_optionvalue, fc_optiontypeid);

		switch (fc_opt->opt_type)
		{
			case PLPGSQL_RAISEOPTION_ERRCODE:
				if (fc_err_code)
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("RAISE option already specified: %s",
									"ERRCODE")));
				fc_err_code = plpgsql_recognize_err_condition(fc_extval, true);
				fc_condname = MemoryContextStrdup(fc_stmt_mcontext, fc_extval);
				break;
			case PLPGSQL_RAISEOPTION_MESSAGE:
				SET_RAISE_OPTION_TEXT(fc_err_message, "MESSAGE");
				break;
			case PLPGSQL_RAISEOPTION_DETAIL:
				SET_RAISE_OPTION_TEXT(fc_err_detail, "DETAIL");
				break;
			case PLPGSQL_RAISEOPTION_HINT:
				SET_RAISE_OPTION_TEXT(fc_err_hint, "HINT");
				break;
			case PLPGSQL_RAISEOPTION_COLUMN:
				SET_RAISE_OPTION_TEXT(fc_err_column, "COLUMN");
				break;
			case PLPGSQL_RAISEOPTION_CONSTRAINT:
				SET_RAISE_OPTION_TEXT(fc_err_constraint, "CONSTRAINT");
				break;
			case PLPGSQL_RAISEOPTION_DATATYPE:
				SET_RAISE_OPTION_TEXT(fc_err_datatype, "DATATYPE");
				break;
			case PLPGSQL_RAISEOPTION_TABLE:
				SET_RAISE_OPTION_TEXT(fc_err_table, "TABLE");
				break;
			case PLPGSQL_RAISEOPTION_SCHEMA:
				SET_RAISE_OPTION_TEXT(fc_err_schema, "SCHEMA");
				break;
			default:
				elog(ERROR, "unrecognized raise option: %d", fc_opt->opt_type);
		}

		fc_exec_eval_cleanup(fc_estate);
	}

	/* 如果没有指定，则使用默认代码 */
	if (fc_err_code == 0 && fc_stmt->elog_level >= ERROR)
		fc_err_code = ERRCODE_RAISE_EXCEPTION;

	/* 如果没有指定，则使用默认错误消息 */
	if (fc_err_message == NULL)
	{
		if (fc_condname)
		{
			fc_err_message = fc_condname;
			fc_condname = NULL;
		}
		else
			fc_err_message = MemoryContextStrdup(fc_stmt_mcontext,
											  unpack_sql_state(fc_err_code));
	}

	/*
	 * 抛出错误（可能会回来，也可能不会回来）
	 */
	ereport(fc_stmt->elog_level,
			(fc_err_code ? errcode(fc_err_code) : 0,
			 errmsg_internal("%s", fc_err_message),
			 (fc_err_detail != NULL) ? errdetail_internal("%s", fc_err_detail) : 0,
			 (fc_err_hint != NULL) ? errhint("%s", fc_err_hint) : 0,
			 (fc_err_column != NULL) ?
			 err_generic_string(PG_DIAG_COLUMN_NAME, fc_err_column) : 0,
			 (fc_err_constraint != NULL) ?
			 err_generic_string(PG_DIAG_CONSTRAINT_NAME, fc_err_constraint) : 0,
			 (fc_err_datatype != NULL) ?
			 err_generic_string(PG_DIAG_DATATYPE_NAME, fc_err_datatype) : 0,
			 (fc_err_table != NULL) ?
			 err_generic_string(PG_DIAG_TABLE_NAME, fc_err_table) : 0,
			 (fc_err_schema != NULL) ?
			 err_generic_string(PG_DIAG_SCHEMA_NAME, fc_err_schema) : 0));

	/* 清理临时字符串 */
	MemoryContextReset(fc_stmt_mcontext);

	return PLPGSQL_RC_OK;
}

/* ----------
 * exec_stmt_assert			断言语句
 * ----------
 */
static int fc_exec_stmt_assert(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_assert *fc_stmt)
{
	bool		fc_value;
	bool		fc_isnull;

	/* 如果未启用断言则不做任何操作 */
	if (!plpgsql_check_asserts)
		return PLPGSQL_RC_OK;

	fc_value = fc_exec_eval_boolean(fc_estate, fc_stmt->cond, &fc_isnull);
	fc_exec_eval_cleanup(fc_estate);

	if (fc_isnull || !fc_value)
	{
		char	   *fc_message = NULL;

		if (fc_stmt->message != NULL)
		{
			Datum		fc_val;
			Oid			fc_typeid;
			int32		fc_typmod;

			fc_val = fc_exec_eval_expr(fc_estate, fc_stmt->message,
								 &fc_isnull, &fc_typeid, &fc_typmod);
			if (!fc_isnull)
				fc_message = fc_convert_value_to_string(fc_estate, fc_val, fc_typeid);
			/* 我们不应该在这里执行 exec_eval_cleanup */
		}

		ereport(ERROR,
				(errcode(ERRCODE_ASSERT_FAILURE),
				 fc_message ? errmsg_internal("%s", fc_message) :
				 errmsg("assertion failed")));
	}

	return PLPGSQL_RC_OK;
}

/* ----------
 * 初始化一个几乎为空的执行状态
 * ----------
 */
static void fc_plpgsql_estate_setup(PLpgSQL_execstate *fc_estate,
					 PLpgSQL_function *fc_func,
					 ReturnSetInfo *fc_rsi,
					 EState *fc_simple_eval_estate,
					 ResourceOwner fc_simple_eval_resowner)
{
	HASHCTL		fc_ctl;

	/* 这个链接将在退出 plpgsql_call_handler 时恢复 */
	fc_func->cur_estate = fc_estate;

	fc_estate->func = fc_func;
	fc_estate->trigdata = NULL;
	fc_estate->evtrigdata = NULL;

	fc_estate->retval = (Datum) 0;
	fc_estate->retisnull = true;
	fc_estate->rettype = InvalidOid;

	fc_estate->fn_rettype = fc_func->fn_rettype;
	fc_estate->retistuple = fc_func->fn_retistuple;
	fc_estate->retisset = fc_func->fn_retset;

	fc_estate->readonly_func = fc_func->fn_readonly;
	fc_estate->atomic = true;

	fc_estate->exitlabel = NULL;
	fc_estate->cur_error = NULL;

	fc_estate->tuple_store = NULL;
	fc_estate->tuple_store_desc = NULL;
	if (fc_rsi)
	{
		fc_estate->tuple_store_cxt = fc_rsi->econtext->ecxt_per_query_memory;
		fc_estate->tuple_store_owner = CurrentResourceOwner;
	}
	else
	{
		fc_estate->tuple_store_cxt = NULL;
		fc_estate->tuple_store_owner = NULL;
	}
	fc_estate->rsi = fc_rsi;

	fc_estate->found_varno = fc_func->found_varno;
	fc_estate->ndatums = fc_func->ndatums;
	fc_estate->datums = NULL;
	/* datums 数组将由 copy_plpgsql_datums() 填充 */
	fc_estate->datum_context = CurrentMemoryContext;

	/* 使用适当的钩子函数初始化我们的 ParamListInfo */
	fc_estate->paramLI = makeParamList(0);
	fc_estate->paramLI->paramFetch = fc_plpgsql_param_fetch;
	fc_estate->paramLI->paramFetchArg = (void *) fc_estate;
	fc_estate->paramLI->paramCompile = fc_plpgsql_param_compile;
	fc_estate->paramLI->paramCompileArg = NULL;	/* 不需要 */
	fc_estate->paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup;
	fc_estate->paramLI->parserSetupArg = NULL; /* 在使用过程中填充 */
	fc_estate->paramLI->numParams = fc_estate->ndatums;

	/* 如果我们还没有创建会话范围的类型转换表达式哈希 */
	if (cast_expr_hash == NULL)
	{
		fc_ctl.keysize = sizeof(plpgsql_CastHashKey);
		fc_ctl.entrysize = sizeof(plpgsql_CastExprHashEntry);
		cast_expr_hash = hash_create("PLpgSQL cast expressions",
									 16,	/* 从小开始并扩展 */
									 &fc_ctl,
									 HASH_ELEM | HASH_BLOBS);
	}

	/* 为使用适当的简单表达式 EState 和类型转换哈希进行设置 */
	if (fc_simple_eval_estate)
	{
		fc_estate->simple_eval_estate = fc_simple_eval_estate;
		/* 私有类型转换哈希仅存在于函数的主要上下文中 */
		fc_ctl.keysize = sizeof(plpgsql_CastHashKey);
		fc_ctl.entrysize = sizeof(plpgsql_CastHashEntry);
		fc_ctl.hcxt = CurrentMemoryContext;
		fc_estate->cast_hash = hash_create("PLpgSQL private cast cache",
										16, /* 从小开始并扩展 */
										&fc_ctl,
										HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
	}
	else
	{
		fc_estate->simple_eval_estate = shared_simple_eval_estate;
		/* 如果我们还没有创建会话范围的类型转换信息哈希表 */
		if (shared_cast_hash == NULL)
		{
			fc_ctl.keysize = sizeof(plpgsql_CastHashKey);
			fc_ctl.entrysize = sizeof(plpgsql_CastHashEntry);
			shared_cast_hash = hash_create("PLpgSQL cast cache",
										   16,	/* 从小开始并扩展 */
										   &fc_ctl,
										   HASH_ELEM | HASH_BLOBS);
		}
		fc_estate->cast_hash = shared_cast_hash;
	}
	/* 对于简单表达式资源拥有者也是如此 */
	if (fc_simple_eval_resowner)
		fc_estate->simple_eval_resowner = fc_simple_eval_resowner;
	else
		fc_estate->simple_eval_resowner = shared_simple_eval_resowner;

	/* 如果有过程资源拥有者，将在稍后填充 */
	fc_estate->procedure_resowner = NULL;

	/*
	 * 我们从没有 stmt_mcontext 开始；只有在需要时才会创建一个。
	 * 该上下文将是函数主要执行上下文的直接子级。
	 * 可能会创建其他 stmt_mcontext 作为它的子级。
	 */
	fc_estate->stmt_mcontext = NULL;
	fc_estate->stmt_mcontext_parent = CurrentMemoryContext;

	fc_estate->eval_tuptable = NULL;
	fc_estate->eval_processed = 0;
	fc_estate->eval_econtext = NULL;

	fc_estate->err_stmt = NULL;
	fc_estate->err_var = NULL;
	fc_estate->err_text = NULL;

	fc_estate->plugin_info = NULL;

	/*
	 * 为简单表达式的评估创建一个 EState 和 ExprContext。
	 */
	fc_plpgsql_create_econtext(fc_estate);

	/*
	 * 让插件（如果有）在我们初始化本地
	 * PL/pgSQL 变量之前看到这个函数。请注意，我们还给插件一些函数
	 * 指针，以便它可以回调 PL/pgSQL 执行诸如
	 * 变量赋值和堆栈跟踪之类的操作。
	 */
	if (*plpgsql_plugin_ptr)
	{
		(*plpgsql_plugin_ptr)->error_callback = fc_plpgsql_exec_error_callback;
		(*plpgsql_plugin_ptr)->assign_expr = fc_exec_assign_expr;
		(*plpgsql_plugin_ptr)->assign_value = fc_exec_assign_value;
		(*plpgsql_plugin_ptr)->eval_datum = fc_exec_eval_datum;
		(*plpgsql_plugin_ptr)->cast_value = fc_exec_cast_value;

		if ((*plpgsql_plugin_ptr)->func_setup)
			((*plpgsql_plugin_ptr)->func_setup) (fc_estate, fc_func);
	}
}

/* ----------
 * 释放表达式/子查询评估使用的临时内存
 *
 * 注意：完成此操作后，评估的结果不再有效，
 * 除非它是按值传递的数据类型。
 * ----------
 */
static void fc_exec_eval_cleanup(PLpgSQL_execstate *fc_estate)
{
	/* 清除完整 SPI_execute 的结果 */
	if (fc_estate->eval_tuptable != NULL)
		SPI_freetuptable(fc_estate->eval_tuptable);
	fc_estate->eval_tuptable = NULL;

	/*
	 * 清除 exec_eval_simple_expr 的结果（但保留 econtext）。这
	 * 还清除了通过 get_eval_mcontext 进行的任何短期分配。
	 */
	if (fc_estate->eval_econtext != NULL)
		ResetExprContext(fc_estate->eval_econtext);
}


/* ----------
 * 生成预编译计划
 *
 * 注意：这个函数在构建 SPIPlan并将其保存到 expr->plan 后
 * 可能会抛出错误。因此，谨慎处理依赖于 expr->plan 为 NULL 的
 * 其他事情。也就是说，给定下面的代码：
 *
 *	if (query->plan == NULL)
 *	{
 *		// 可以在这里放置设置代码
 *		exec_prepare_plan(estate, query, ...);
 *		// 不可以在这里放置更多逻辑
 *	}
 *
 * 额外的步骤在最后是不安全的，因为如果 exec_prepare_plan
 * 第一次失败，则在重新执行调用语句时不会执行这些步骤。这
 * 实在是令人烦恼的易错，但替代方案更糟糕。
 * ----------
 */
static void fc_exec_prepare_plan(PLpgSQL_execstate *fc_estate,
				  PLpgSQL_expr *fc_expr, int fc_cursorOptions)
{
	SPIPlanPtr	fc_plan;
	SPIPrepareOptions fc_options;

	/*
	 * 由于语法树构建时无法方便地设置 expr->func，
	 * 因此在解析器挂钩需要它之前确保已设置。
	 */
	fc_expr->func = fc_estate->func;

	/*
	 * 生成并保存计划
	 */
	memset(&fc_options, 0, sizeof(fc_options));
	fc_options.parserSetup = (ParserSetupHook) plpgsql_parser_setup;
	fc_options.parserSetupArg = (void *) fc_expr;
	fc_options.parseMode = fc_expr->parseMode;
	fc_options.cursorOptions = fc_cursorOptions;
	fc_plan = SPI_prepare_extended(fc_expr->query, &fc_options);
	if (fc_plan == NULL)
		elog(ERROR, "SPI_prepare_extended failed for \"%s\": %s",
			 fc_expr->query, SPI_result_code_string(SPI_result));

	SPI_keepplan(fc_plan);
	fc_expr->plan = fc_plan;

	/* 检查它是否是简单表达式 */
	fc_exec_simple_check_plan(fc_estate, fc_expr);
}


/* ----------
 * exec_stmt_execsql			执行 SQL 语句（可能带有 INTO）。
 *
 * 注意：一些调用者依赖于此不会触及 stmt_mcontext。如果它
 * 需要使用它，请修复这些调用者以推送/弹出 stmt_mcontext。
 * ----------
 */
static int fc_exec_stmt_execsql(PLpgSQL_execstate *fc_estate,
				  PLpgSQL_stmt_execsql *fc_stmt)
{
	ParamListInfo fc_paramLI;
	long		fc_tcount;
	int			fc_rc;
	PLpgSQL_expr *fc_expr = fc_stmt->sqlstmt;
	int			fc_too_many_rows_level = 0;

	if (plpgsql_extra_errors & PLPGSQL_XCHECK_TOOMANYROWS)
		fc_too_many_rows_level = ERROR;
	else if (plpgsql_extra_warnings & PLPGSQL_XCHECK_TOOMANYROWS)
		fc_too_many_rows_level = WARNING;

	/*
	 * 在此语句的第一次调用中生成计划，并检测
	 * 语句是否为 INSERT/UPDATE/DELETE/MERGE
	 */
	if (fc_expr->plan == NULL)
		fc_exec_prepare_plan(fc_estate, fc_expr, CURSOR_OPT_PARALLEL_OK);

	if (!fc_stmt->mod_stmt_set)
	{
		ListCell   *fc_l;

		fc_stmt->mod_stmt = false;
		foreach(fc_l, SPI_plan_get_plan_sources(fc_expr->plan))
		{
			CachedPlanSource *fc_plansource = (CachedPlanSource *) lfirst(fc_l);

			/*
			 * 我们可以查看 raw_parse_tree，但检查命令标签似乎更简单。
			 * 注意，我们不应该查看 Query
			 * 树，因为它们是重写的结果，可能是
			 * 陈旧的，或者可能完全变成其他东西。
			 */
			if (fc_plansource->commandTag == CMDTAG_INSERT ||
				fc_plansource->commandTag == CMDTAG_UPDATE ||
				fc_plansource->commandTag == CMDTAG_DELETE ||
				fc_plansource->commandTag == CMDTAG_MERGE)
			{
				fc_stmt->mod_stmt = true;
				break;
			}
		}
		fc_stmt->mod_stmt_set = true;
	}

	/*
	 * 设置 ParamListInfo 以传递给执行器
	 */
	fc_paramLI = fc_setup_param_list(fc_estate, fc_expr);

	/*
	 * 如果我们有 INTO，则我们只需要返回一行……但是如果我们有 INTO
	 * STRICT 或额外检查 too_many_rows，则请求两行，以便我们可以
	 * 验证语句仅返回一行。INSERT/UPDATE/DELETE 始终
	 * 被视为严格的。没有 INTO，只需运行语句到完成
	 * （tcount = 0）。
	 *
	 * 使用 INTO 时，我们可以始终请求两行，但在某些情况下，
	 * 强制要求额外一行会耗费显著时间，例如通过
	 * 强制完成顺序扫描。因此，除非我们需要
	 * 强制严格性，否则不要这样做。
	 */
	if (fc_stmt->into)
	{
		if (fc_stmt->strict || fc_stmt->mod_stmt || fc_too_many_rows_level)
			fc_tcount = 2;
		else
			fc_tcount = 1;
	}
	else
		fc_tcount = 0;

	/*
	 * 执行计划
	 */
	fc_rc = SPI_execute_plan_with_paramlist(fc_expr->plan, fc_paramLI,
										 fc_estate->readonly_func, fc_tcount);

	/*
	 * 检查错误，如果适当则设置 FOUND（出于历史原因，我们只针对某些查询类型设置 FOUND）。同时，断言我们识别的语句类型与 SPI 一致。
	 */
	switch (fc_rc)
	{
		case SPI_OK_SELECT:
			Assert(!fc_stmt->mod_stmt);
			fc_exec_set_found(fc_estate, (SPI_processed != 0));
			break;

		case SPI_OK_INSERT:
		case SPI_OK_UPDATE:
		case SPI_OK_DELETE:
		case SPI_OK_INSERT_RETURNING:
		case SPI_OK_UPDATE_RETURNING:
		case SPI_OK_DELETE_RETURNING:
		case SPI_OK_MERGE:
			Assert(fc_stmt->mod_stmt);
			fc_exec_set_found(fc_estate, (SPI_processed != 0));
			break;

		case SPI_OK_SELINTO:
		case SPI_OK_UTILITY:
			Assert(!fc_stmt->mod_stmt);
			break;

		case SPI_OK_REWRITTEN:

			/*
			 * 命令已重写为另一种命令。在这种情况下，不清楚 FOUND 的意义是什么（而且 SPI 也不返回行数），所以将其设置为 false。注意，我们在这里不能对 mod_stmt 断言任何内容。
			 */
			fc_exec_set_found(fc_estate, false);
			break;

			/* 一些 SPI 错误值得特定的错误消息 */
		case SPI_ERROR_COPY:
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("cannot COPY to/from client in PL/pgSQL")));
			break;

		case SPI_ERROR_TRANSACTION:
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("unsupported transaction command in PL/pgSQL")));
			break;

		default:
			elog(ERROR, "SPI_execute_plan_with_paramlist failed executing query \"%s\": %s",
				 fc_expr->query, SPI_result_code_string(fc_rc));
			break;
	}

	/* 所有变体应为 GET DIAGNOSTICS 保存结果信息 */
	fc_estate->eval_processed = SPI_processed;

	/* 如果存在则处理 INTO */
	if (fc_stmt->into)
	{
		SPITupleTable *fc_tuptab = SPI_tuptable;
		uint64		fc_n = SPI_processed;
		PLpgSQL_variable *fc_target;

		/* 如果语句未返回元组表，则抱怨 */
		if (fc_tuptab == NULL)
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("INTO used with a command that cannot return data")));

		/* 获取目标的数据条目 */
		fc_target = (PLpgSQL_variable *) fc_estate->datums[fc_stmt->target->dno];

		/*
		 * 如果 SELECT ... INTO 指定为 STRICT，并且查询未找到
		 * 正好一行，则抛出错误。如果未指定 STRICT，则允许查询找到任意数量的行。
		 */
		if (fc_n == 0)
		{
			if (fc_stmt->strict)
			{
				char	   *fc_errdetail;

				if (fc_estate->func->print_strict_params)
					fc_errdetail = fc_format_expr_params(fc_estate, fc_expr);
				else
					fc_errdetail = NULL;

				ereport(ERROR,
						(errcode(ERRCODE_NO_DATA_FOUND),
						 errmsg("query returned no rows"),
						 fc_errdetail ? errdetail_internal("parameters: %s", fc_errdetail) : 0));
			}
			/* 将目标设置为 NULL(s) */
			fc_exec_move_row(fc_estate, fc_target, NULL, fc_tuptab->tupdesc);
		}
		else
		{
			if (fc_n > 1 && (fc_stmt->strict || fc_stmt->mod_stmt || fc_too_many_rows_level))
			{
				char	   *fc_errdetail;
				int			fc_errlevel;

				if (fc_estate->func->print_strict_params)
					fc_errdetail = fc_format_expr_params(fc_estate, fc_expr);
				else
					fc_errdetail = NULL;

				fc_errlevel = (fc_stmt->strict || fc_stmt->mod_stmt) ? ERROR : fc_too_many_rows_level;

				ereport(fc_errlevel,
						(errcode(ERRCODE_TOO_MANY_ROWS),
						 errmsg("query returned more than one row"),
						 fc_errdetail ? errdetail_internal("parameters: %s", fc_errdetail) : 0,
						 errhint("Make sure the query returns a single row, or use LIMIT 1.")));
			}
			/* 将第一结果行放入目标 */
			fc_exec_move_row(fc_estate, fc_target, fc_tuptab->vals[0], fc_tuptab->tupdesc);
		}

		/* 清理 */
		fc_exec_eval_cleanup(fc_estate);
		SPI_freetuptable(SPI_tuptable);
	}
	else
	{
		/* 如果语句返回了元组表，则抱怨 */
		if (SPI_tuptable != NULL)
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("query has no destination for result data"),
					 (fc_rc == SPI_OK_SELECT) ? errhint("If you want to discard the results of a SELECT, use PERFORM instead.") : 0));
	}

	return PLPGSQL_RC_OK;
}


/* ----------
 * exec_stmt_dynexecute			执行动态 SQL 查询
 *					（可能带有 INTO）。
 * ----------
 */
static int fc_exec_stmt_dynexecute(PLpgSQL_execstate *fc_estate,
					 PLpgSQL_stmt_dynexecute *fc_stmt)
{
	Datum		fc_query;
	bool		fc_isnull;
	Oid			fc_restype;
	int32		fc_restypmod;
	char	   *fc_querystr;
	int			fc_exec_res;
	ParamListInfo fc_paramLI;
	SPIExecuteOptions fc_options;
	MemoryContext fc_stmt_mcontext = fc_get_stmt_mcontext(fc_estate);

	/*
	 * 首先我们评估 EXECUTE 关键字后的字符串表达式。它的
	 * 结果是我们必须执行的查询字符串。
	 */
	fc_query = fc_exec_eval_expr(fc_estate, fc_stmt->query, &fc_isnull, &fc_restype, &fc_restypmod);
	if (fc_isnull)
		ereport(ERROR,
				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
				 errmsg("query string argument of EXECUTE is null")));

	/* 获取 C 字符串表示 */
	fc_querystr = fc_convert_value_to_string(fc_estate, fc_query, fc_restype);

	/* 在清理之前将其复制到 stmt_mcontext */
	fc_querystr = MemoryContextStrdup(fc_stmt_mcontext, fc_querystr);

	fc_exec_eval_cleanup(fc_estate);

	/*
	 * 在不准备保存的计划的情况下执行查询。
	 */
	fc_paramLI = fc_exec_eval_using_params(fc_estate, fc_stmt->params);

	memset(&fc_options, 0, sizeof(fc_options));
	fc_options.params = fc_paramLI;
	fc_options.read_only = fc_estate->readonly_func;

	fc_exec_res = SPI_execute_extended(fc_querystr, &fc_options);

	switch (fc_exec_res)
	{
		case SPI_OK_SELECT:
		case SPI_OK_INSERT:
		case SPI_OK_UPDATE:
		case SPI_OK_DELETE:
		case SPI_OK_INSERT_RETURNING:
		case SPI_OK_UPDATE_RETURNING:
		case SPI_OK_DELETE_RETURNING:
		case SPI_OK_MERGE:
		case SPI_OK_UTILITY:
		case SPI_OK_REWRITTEN:
			break;

		case 0:

			/*
			 * 还允许返回零，意味着查询字符串
			 * 不包含任何命令。
			 */
			break;

		case SPI_OK_SELINTO:

			/*
			 * 我们暂时不想允许 SELECT INTO，因为它的行为
			 * 与普通 plpgsql 上下文中的 SELECT INTO 不一致。
			 * （我们需要重新实现 EXECUTE，以将字符串解析为
			 * plpgsql 命令，而不仅仅是将其提供给 SPI_execute。）这并不是
			 * 功能上的限制，因为允许 CREATE TABLE AS。
			 */
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("EXECUTE of SELECT ... INTO is not implemented"),
					 errhint("You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead.")));
			break;

			/* 一些 SPI 错误值得特定的错误消息 */
		case SPI_ERROR_COPY:
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("cannot COPY to/from client in PL/pgSQL")));
			break;

		case SPI_ERROR_TRANSACTION:
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("EXECUTE of transaction commands is not implemented")));
			break;

		default:
			elog(ERROR, "SPI_execute_extended failed executing query \"%s\": %s",
				 fc_querystr, SPI_result_code_string(fc_exec_res));
			break;
	}

	/* 为 GET DIAGNOSTICS 保存结果信息 */
	fc_estate->eval_processed = SPI_processed;

	/* 如果存在则处理 INTO */
	if (fc_stmt->into)
	{
		SPITupleTable *fc_tuptab = SPI_tuptable;
		uint64		fc_n = SPI_processed;
		PLpgSQL_variable *fc_target;

		/* 如果语句未返回元组表，则抱怨 */
		if (fc_tuptab == NULL)
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("INTO used with a command that cannot return data")));

		/* 获取目标的数据条目 */
		fc_target = (PLpgSQL_variable *) fc_estate->datums[fc_stmt->target->dno];

		/*
		 * 如果 SELECT ... INTO 指定为 STRICT，并且查询未找到
		 * 正好一行，则抛出错误。如果未指定 STRICT，则允许查询找到任意数量的行。
		 */
		if (fc_n == 0)
		{
			if (fc_stmt->strict)
			{
				char	   *fc_errdetail;

				if (fc_estate->func->print_strict_params)
					fc_errdetail = fc_format_preparedparamsdata(fc_estate, fc_paramLI);
				else
					fc_errdetail = NULL;

				ereport(ERROR,
						(errcode(ERRCODE_NO_DATA_FOUND),
						 errmsg("query returned no rows"),
						 fc_errdetail ? errdetail_internal("parameters: %s", fc_errdetail) : 0));
			}
			/* 将目标设置为 NULL(s) */
			fc_exec_move_row(fc_estate, fc_target, NULL, fc_tuptab->tupdesc);
		}
		else
		{
			if (fc_n > 1 && fc_stmt->strict)
			{
				char	   *fc_errdetail;

				if (fc_estate->func->print_strict_params)
					fc_errdetail = fc_format_preparedparamsdata(fc_estate, fc_paramLI);
				else
					fc_errdetail = NULL;

				ereport(ERROR,
						(errcode(ERRCODE_TOO_MANY_ROWS),
						 errmsg("query returned more than one row"),
						 fc_errdetail ? errdetail_internal("parameters: %s", fc_errdetail) : 0));
			}

			/* 将第一结果行放入目标 */
			fc_exec_move_row(fc_estate, fc_target, fc_tuptab->vals[0], fc_tuptab->tupdesc);
		}
		/* 在exec_move_row()之后进行清理 */
		fc_exec_eval_cleanup(fc_estate);
	}
	else
	{
		/*
		 * 如果查询返回的元组被忽略，抛出错误可能是个好主意，但历史上我们并没有这样做。
		 */
	}

	/* 释放来自 SPI_execute 的任何结果，以及瞬态数据 */
	SPI_freetuptable(SPI_tuptable);
	MemoryContextReset(fc_stmt_mcontext);

	return PLPGSQL_RC_OK;
}


/* ----------
 * exec_stmt_dynfors			执行动态查询，将每个
 *					元组分配给记录或行并
 *					为其执行一组语句
 * ----------
 */
static int fc_exec_stmt_dynfors(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_dynfors *fc_stmt)
{
	Portal		fc_portal;
	int			fc_rc;

	fc_portal = fc_exec_dynquery_with_params(fc_estate, fc_stmt->query, fc_stmt->params,
									   NULL, CURSOR_OPT_NO_SCROLL);

	/*
	 * 执行循环
	 */
	fc_rc = fc_exec_for_query(fc_estate, (PLpgSQL_stmt_forq *) fc_stmt, fc_portal, true);

	/*
	 * 关闭隐式游标
	 */
	SPI_cursor_close(fc_portal);

	return fc_rc;
}


/* ----------
 * exec_stmt_open			执行OPEN游标语句
 * ----------
 */
static int fc_exec_stmt_open(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_open *fc_stmt)
{
	PLpgSQL_var *fc_curvar;
	MemoryContext fc_stmt_mcontext = NULL;
	char	   *fc_curname = NULL;
	PLpgSQL_expr *fc_query;
	Portal		fc_portal;
	ParamListInfo fc_paramLI;

	/* ----------
	 * 获取游标变量，如果它有分配的名称，检查
	 * 它是否当前未在使用。
	 * ----------
	 */
	fc_curvar = (PLpgSQL_var *) (fc_estate->datums[fc_stmt->curvar]);
	if (!fc_curvar->isnull)
	{
		MemoryContext fc_oldcontext;

		/* 我们只需要 stmt_mcontext 来保存游标名称字符串 */
		fc_stmt_mcontext = fc_get_stmt_mcontext(fc_estate);
		fc_oldcontext = MemoryContextSwitchTo(fc_stmt_mcontext);
		fc_curname = TextDatumGetCString(fc_curvar->value);
		MemoryContextSwitchTo(fc_oldcontext);

		if (SPI_cursor_find(fc_curname) != NULL)
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_CURSOR),
					 errmsg("cursor \"%s\" already in use", fc_curname)));
	}

	/* ----------
	 * 根据类型处理OPEN。
	 * ----------
	 */
	if (fc_stmt->query != NULL)
	{
		/* ----------
		 * 这是一个OPEN refcursor FOR SELECT ...
		 *
		 * 我们只是确保查询已经计划好了。真正的工作
		 * 在下面完成。
		 * ----------
		 */
		fc_query = fc_stmt->query;
		if (fc_query->plan == NULL)
			fc_exec_prepare_plan(fc_estate, fc_query, fc_stmt->cursor_options);
	}
	else if (fc_stmt->dynquery != NULL)
	{
		/* ----------
		 * 这是一个OPEN refcursor FOR EXECUTE ...
		 * ----------
		 */
		fc_portal = fc_exec_dynquery_with_params(fc_estate,
										   fc_stmt->dynquery,
										   fc_stmt->params,
										   fc_curname,
										   fc_stmt->cursor_options);

		/*
		 * 如果游标变量为NULL，在确认可以分配后
		 * 将生成的门户名称存储到其中。
		 *
		 * 注意：exec_dynquery_with_params已经重置了stmt_mcontext，因此
		 * curname在这里是一个悬挂指针；但测试其是否为null是
		 * 可以的。
		 */
		if (fc_curname == NULL)
		{
			fc_exec_check_assignable(fc_estate, fc_stmt->curvar);
			fc_assign_text_var(fc_estate, fc_curvar, fc_portal->name);
		}

		return PLPGSQL_RC_OK;
	}
	else
	{
		/* ----------
		 * 这是一个OPEN游标
		 *
		 * 注意：解析器应该已经检查过语句是否提供
		 * 参数，如果游标需要它们，但我们再次检查以确保安全。
		 * ----------
		 */
		if (fc_stmt->argquery != NULL)
		{
			/* ----------
			 * 带参数的OPEN CURSOR。我们伪造一个SELECT ... INTO ...
			 * 语句来评估参数并将其放入
			 * 内部行中。
			 * ----------
			 */
			PLpgSQL_stmt_execsql fc_set_args;

			if (fc_curvar->cursor_explicit_argrow < 0)
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("arguments given for cursor without arguments")));

			memset(&fc_set_args, 0, sizeof(fc_set_args));
			fc_set_args.cmd_type = PLPGSQL_STMT_EXECSQL;
			fc_set_args.lineno = fc_stmt->lineno;
			fc_set_args.sqlstmt = fc_stmt->argquery;
			fc_set_args.into = true;
			/* XXX 历史上这并不是严格的 */
			fc_set_args.target = (PLpgSQL_variable *)
				(fc_estate->datums[fc_curvar->cursor_explicit_argrow]);

			if (fc_exec_stmt_execsql(fc_estate, &fc_set_args) != PLPGSQL_RC_OK)
				elog(ERROR, "open cursor failed during argument processing");
		}
		else
		{
			if (fc_curvar->cursor_explicit_argrow >= 0)
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("arguments required for cursor")));
		}

		fc_query = fc_curvar->cursor_explicit_expr;
		if (fc_query->plan == NULL)
			fc_exec_prepare_plan(fc_estate, fc_query, fc_curvar->cursor_options);
	}

	/*
	 * 为这个查询设置 ParamListInfo
	 */
	fc_paramLI = fc_setup_param_list(fc_estate, fc_query);

	/*
	 * 打开游标（参数列表将被复制到门户中）
	 */
	fc_portal = SPI_cursor_open_with_paramlist(fc_curname, fc_query->plan,
											fc_paramLI,
											fc_estate->readonly_func);
	if (fc_portal == NULL)
		elog(ERROR, "could not open cursor: %s",
			 SPI_result_code_string(SPI_result));

	/*
	 * 如果游标变量为 NULL，将生成的门户名称存储其中，
	 * 在验证可以赋值后进行。
	 */
	if (fc_curname == NULL)
	{
		fc_exec_check_assignable(fc_estate, fc_stmt->curvar);
		fc_assign_text_var(fc_estate, fc_curvar, fc_portal->name);
	}

	/* 如果我们有任何瞬态数据，清理它 */
	fc_exec_eval_cleanup(fc_estate);
	if (fc_stmt_mcontext)
		MemoryContextReset(fc_stmt_mcontext);

	return PLPGSQL_RC_OK;
}


/* ----------
 * exec_stmt_fetch			从游标获取到目标，或者只是
 *							移动游标的当前位置
 * ----------
 */
static int fc_exec_stmt_fetch(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_fetch *fc_stmt)
{
	PLpgSQL_var *fc_curvar;
	long		fc_how_many = fc_stmt->how_many;
	SPITupleTable *fc_tuptab;
	Portal		fc_portal;
	char	   *fc_curname;
	uint64		fc_n;
	MemoryContext fc_oldcontext;

	/* ----------
	 * 通过名称获取游标的门户
	 * ----------
	 */
	fc_curvar = (PLpgSQL_var *) (fc_estate->datums[fc_stmt->curvar]);
	if (fc_curvar->isnull)
		ereport(ERROR,
				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
				 errmsg("cursor variable \"%s\" is null", fc_curvar->refname)));

	/* 使用 eval_mcontext 处理短生命周期字符串 */
	fc_oldcontext = MemoryContextSwitchTo(get_eval_mcontext(fc_estate));
	fc_curname = TextDatumGetCString(fc_curvar->value);
	MemoryContextSwitchTo(fc_oldcontext);

	fc_portal = SPI_cursor_find(fc_curname);
	if (fc_portal == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_CURSOR),
				 errmsg("cursor \"%s\" does not exist", fc_curname)));

	/* 计算FETCH_RELATIVE或FETCH_ABSOLUTE的位置 */
	if (fc_stmt->expr)
	{
		bool		fc_isnull;

		/* XXX 应该使用LONG而不是INT宽度来完成这个 */
		fc_how_many = fc_exec_eval_integer(fc_estate, fc_stmt->expr, &fc_isnull);

		if (fc_isnull)
			ereport(ERROR,
					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
					 errmsg("relative or absolute cursor position is null")));

		fc_exec_eval_cleanup(fc_estate);
	}

	if (!fc_stmt->is_move)
	{
		PLpgSQL_variable *fc_target;

		/* ----------
		 * 从游标提取1个元组
		 * ----------
		 */
		SPI_scroll_cursor_fetch(fc_portal, fc_stmt->direction, fc_how_many);
		fc_tuptab = SPI_tuptable;
		fc_n = SPI_processed;

		/* ----------
		 * 适当设置目标。
		 * ----------
		 */
		fc_target = (PLpgSQL_variable *) fc_estate->datums[fc_stmt->target->dno];
		if (fc_n == 0)
			fc_exec_move_row(fc_estate, fc_target, NULL, fc_tuptab->tupdesc);
		else
			fc_exec_move_row(fc_estate, fc_target, fc_tuptab->vals[0], fc_tuptab->tupdesc);

		fc_exec_eval_cleanup(fc_estate);
		SPI_freetuptable(fc_tuptab);
	}
	else
	{
		/* 移动游标 */
		SPI_scroll_cursor_move(fc_portal, fc_stmt->direction, fc_how_many);
		fc_n = SPI_processed;
	}

	/* 适当设置ROW_COUNT和全局FOUND变量。 */
	fc_estate->eval_processed = fc_n;
	fc_exec_set_found(fc_estate, fc_n != 0);

	return PLPGSQL_RC_OK;
}

/* ----------
 * exec_stmt_close			关闭游标
 * ----------
 */
static int fc_exec_stmt_close(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_close *fc_stmt)
{
	PLpgSQL_var *fc_curvar;
	Portal		fc_portal;
	char	   *fc_curname;
	MemoryContext fc_oldcontext;

	/* ----------
	 * 通过名称获取游标的门户
	 * ----------
	 */
	fc_curvar = (PLpgSQL_var *) (fc_estate->datums[fc_stmt->curvar]);
	if (fc_curvar->isnull)
		ereport(ERROR,
				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
				 errmsg("cursor variable \"%s\" is null", fc_curvar->refname)));

	/* 使用 eval_mcontext 处理短生命周期字符串 */
	fc_oldcontext = MemoryContextSwitchTo(get_eval_mcontext(fc_estate));
	fc_curname = TextDatumGetCString(fc_curvar->value);
	MemoryContextSwitchTo(fc_oldcontext);

	fc_portal = SPI_cursor_find(fc_curname);
	if (fc_portal == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_CURSOR),
				 errmsg("cursor \"%s\" does not exist", fc_curname)));

	/* ----------
	 * 并关闭它。
	 * ----------
	 */
	SPI_cursor_close(fc_portal);

	return PLPGSQL_RC_OK;
}

/*
 * exec_stmt_commit
 *
 * 提交事务。
 */
static int fc_exec_stmt_commit(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_commit *fc_stmt)
{
	if (fc_stmt->chain)
		SPI_commit_and_chain();
	else
		SPI_commit();

	/*
	 * 我们需要建立新的简单表达式架构，因为旧的数据结构已经不存在了。
	 */
	fc_estate->simple_eval_estate = NULL;
	fc_estate->simple_eval_resowner = NULL;
	fc_plpgsql_create_econtext(fc_estate);

	return PLPGSQL_RC_OK;
}

/*
 * exec_stmt_rollback
 *
 * 中止事务。
 */
static int fc_exec_stmt_rollback(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_rollback *fc_stmt)
{
	if (fc_stmt->chain)
		SPI_rollback_and_chain();
	else
		SPI_rollback();

	/*
	 * 我们需要建立新的简单表达式架构，因为旧的数据结构已经不存在了。
	 */
	fc_estate->simple_eval_estate = NULL;
	fc_estate->simple_eval_resowner = NULL;
	fc_plpgsql_create_econtext(fc_estate);

	return PLPGSQL_RC_OK;
}

/* ----------
 * exec_assign_expr			将表达式的结果放入变量中。
 * ----------
 */
static void fc_exec_assign_expr(PLpgSQL_execstate *fc_estate, PLpgSQL_datum *fc_target,
				 PLpgSQL_expr *fc_expr)
{
	Datum		fc_value;
	bool		fc_isnull;
	Oid			fc_valtype;
	int32		fc_valtypmod;

	/*
	 * 如果是第一次执行，创建此表达式的计划。
	 */
	if (fc_expr->plan == NULL)
	{
		/*
		 * 如果目标是简单变量，则将表达式标记为赋值源。
		 * （这有点麻烦，但似乎比为了这个目的修改exec_prepare_plan的API更干净。
		 * 我们必须将目标dno存储到expr中，以便在需要重新规划时可用。）
		 */
		if (fc_target->dtype == PLPGSQL_DTYPE_VAR)
			fc_expr->target_param = fc_target->dno;
		else
			fc_expr->target_param = -1;	/* 应该是这样 */

		fc_exec_prepare_plan(fc_estate, fc_expr, 0);
	}

	fc_value = fc_exec_eval_expr(fc_estate, fc_expr, &fc_isnull, &fc_valtype, &fc_valtypmod);
	fc_exec_assign_value(fc_estate, fc_target, fc_value, fc_isnull, fc_valtype, fc_valtypmod);
	fc_exec_eval_cleanup(fc_estate);
}


/* ----------
 * exec_assign_c_string		将C字符串放入文本变量中。
 *
 * 我们将NULL指针视为空字符串，而不是SQL空值。
 *
 * 与底层的exec_assign_value一样，调用者应该在稍后执行
 * exec_eval_cleanup。
 * ----------
 */
static void fc_exec_assign_c_string(PLpgSQL_execstate *fc_estate, PLpgSQL_datum *fc_target,
					 const char *fc_str)
{
	text	   *fc_value;
	MemoryContext fc_oldcontext;

	/* 使用eval_mcontext用于短期文本值 */
	fc_oldcontext = MemoryContextSwitchTo(get_eval_mcontext(fc_estate));
	if (fc_str != NULL)
		fc_value = cstring_to_text(fc_str);
	else
		fc_value = cstring_to_text("");
	MemoryContextSwitchTo(fc_oldcontext);

	fc_exec_assign_value(fc_estate, fc_target, PointerGetDatum(fc_value), false,
					  TEXTOID, -1);
}


/* ----------
 * exec_assign_value			将值放入目标数据项
 *
 * 注意：在某些代码路径中，这将在eval_mcontext中泄漏内存；
 * 我们假设稍后将通过exec_eval_cleanup进行清理。
 * 我们不能在这里调用exec_eval_cleanup，以免破坏输入Datum值。
 * ----------
 */
static void fc_exec_assign_value(PLpgSQL_execstate *fc_estate,
				  PLpgSQL_datum *fc_target,
				  Datum fc_value, bool fc_isNull,
				  Oid fc_valtype, int32 fc_valtypmod)
{
	switch (fc_target->dtype)
	{
		case PLPGSQL_DTYPE_VAR:
		case PLPGSQL_DTYPE_PROMISE:
			{
				/*
				 * 目标是一个变量
				 */
				PLpgSQL_var *fc_var = (PLpgSQL_var *) fc_target;
				Datum		fc_newvalue;

				fc_newvalue = fc_exec_cast_value(fc_estate,
										   fc_value,
										   &fc_isNull,
										   fc_valtype,
										   fc_valtypmod,
										   fc_var->datatype->typoid,
										   fc_var->datatype->atttypmod);

				if (fc_isNull && fc_var->notnull)
					ereport(ERROR,
							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
							 errmsg("null value cannot be assigned to variable \"%s\" declared NOT NULL",
									fc_var->refname)));

				/*
				 * 如果类型是按引用传递，将新值（它可能在eval_mcontext中）
				 * 复制到过程的主内存上下文中。但如果它是对扩展对象的读/写引用，
				 * 则不需要进行物理复制；最多我们只需重新分配对象的内存上下文。
				 *
				 * 如果它是一个数组，我们强制将值以读/写扩展形式存储。
				 * 如果函数稍后对变量执行了很多数组下标操作，这将很有利， 
				 * 否则可能会失去。我们可能需要使用不同的启发式方法，
				 * 但是现在还为时已晚。此外，是否存在强制非数组值
				 * 进入扩展形式的有用情况？
				 */
				if (!fc_var->datatype->typbyval && !fc_isNull)
				{
					if (fc_var->datatype->typisarray &&
						!VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(fc_newvalue)))
					{
						/* 数组且尚未为读/写，因此应用expand_array */
						fc_newvalue = expand_array(fc_newvalue,
												fc_estate->datum_context,
												NULL);
					}
					else
					{
						/* 否则如果是读/写则传输值，否则仅进行datumCopy */
						fc_newvalue = datumTransfer(fc_newvalue,
												 false,
												 fc_var->datatype->typlen);
					}
				}

				/*
				 * 现在释放旧值（如果有），并分配新值。但如果旧值和新值相同，则跳过赋值。
				 * 注意，对于扩展对象，这一测试是必要的，且不能可靠地在任何早期进行；
				 * 我们必须查看对象的标准读/写指针，以确保指针
				 * 相等是有意义的。
				 *
				 * 此外，如果它是一个承诺变量，我们应该在任何情况下解除
				 * 承诺 —— 否则，将null赋值给已激活的承诺变量将无法
				 * 解除承诺。
				 */
				if (fc_var->value != fc_newvalue || fc_var->isnull || fc_isNull)
					fc_assign_simple_var(fc_estate, fc_var, fc_newvalue, fc_isNull,
									  (!fc_var->datatype->typbyval && !fc_isNull));
				else
					fc_var->promise = PLPGSQL_PROMISE_NONE;
				break;
			}

		case PLPGSQL_DTYPE_ROW:
			{
				/*
				 * 目标是行变量
				 */
				PLpgSQL_row *fc_row = (PLpgSQL_row *) fc_target;

				if (fc_isNull)
				{
					/* 如果源为空，只需将空值分配给行 */
					fc_exec_move_row(fc_estate, (PLpgSQL_variable *) fc_row,
								  NULL, NULL);
				}
				else
				{
					/* 源必须是 RECORD 或复合类型 */
					if (!type_is_rowtype(fc_valtype))
						ereport(ERROR,
								(errcode(ERRCODE_DATATYPE_MISMATCH),
								 errmsg("cannot assign non-composite value to a row variable")));
					fc_exec_move_row_from_datum(fc_estate, (PLpgSQL_variable *) fc_row,
											 fc_value);
				}
				break;
			}

		case PLPGSQL_DTYPE_REC:
			{
				/*
				 * 目标是记录变量
				 */
				PLpgSQL_rec *fc_rec = (PLpgSQL_rec *) fc_target;

				if (fc_isNull)
				{
					if (fc_rec->notnull)
						ereport(ERROR,
								(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
								 errmsg("null value cannot be assigned to variable \"%s\" declared NOT NULL",
										fc_rec->refname)));

					/* 将变量设置为简单的 NULL */
					fc_exec_move_row(fc_estate, (PLpgSQL_variable *) fc_rec,
								  NULL, NULL);
				}
				else
				{
					/* 源必须是 RECORD 或复合类型 */
					if (!type_is_rowtype(fc_valtype))
						ereport(ERROR,
								(errcode(ERRCODE_DATATYPE_MISMATCH),
								 errmsg("cannot assign non-composite value to a record variable")));
					fc_exec_move_row_from_datum(fc_estate, (PLpgSQL_variable *) fc_rec,
											 fc_value);
				}
				break;
			}

		case PLPGSQL_DTYPE_RECFIELD:
			{
				/*
				 * 目标是记录的字段
				 */
				PLpgSQL_recfield *fc_recfield = (PLpgSQL_recfield *) fc_target;
				PLpgSQL_rec *fc_rec;
				ExpandedRecordHeader *fc_erh;

				fc_rec = (PLpgSQL_rec *) (fc_estate->datums[fc_recfield->recparentno]);
				fc_erh = fc_rec->erh;

				/*
				 * 如果记录变量为 NULL，如果它有一个
				 * 命名的复合类型，则实例化它，否则进行抱怨。 （这不会改变
				 * 记录的逻辑状态，但如果我们下面成功分配，未分配的字段将全部变为 NULL。）
				 */
				if (fc_erh == NULL)
				{
					fc_instantiate_empty_record_variable(fc_estate, fc_rec);
					fc_erh = fc_rec->erh;
				}

				/*
				 * 如果我们尚未查找字段的属性，或者
				 * 如果元组描述符 ID 自上次以来发生变化。
				 */
				if (unlikely(fc_recfield->rectupledescid != fc_erh->er_tupdesc_id))
				{
					if (!expanded_record_lookup_field(fc_erh,
													  fc_recfield->fieldname,
													  &fc_recfield->finfo))
						ereport(ERROR,
								(errcode(ERRCODE_UNDEFINED_COLUMN),
								 errmsg("record \"%s\" has no field \"%s\"",
										fc_rec->refname, fc_recfield->fieldname)));
					fc_recfield->rectupledescid = fc_erh->er_tupdesc_id;
				}

				/* 我们不支持对系统列的赋值。 */
				if (fc_recfield->finfo.fnumber <= 0)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("cannot assign to system column \"%s\"",
									fc_recfield->fieldname)));

				/* 如有必要，将新值转换为正确的类型。 */
				fc_value = fc_exec_cast_value(fc_estate,
										fc_value,
										&fc_isNull,
										fc_valtype,
										fc_valtypmod,
										fc_recfield->finfo.ftypeid,
										fc_recfield->finfo.ftypmod);

				/* 并进行赋值。 */
				expanded_record_set_field(fc_erh, fc_recfield->finfo.fnumber,
										  fc_value, fc_isNull, !fc_estate->atomic);
				break;
			}

		default:
			elog(ERROR, "unrecognized dtype: %d", fc_target->dtype);
	}
}

/*
 * exec_eval_datum				获取 PLpgSQL_datum 的当前值
 *
 * 返回类型 oid、typmod、Datum 格式的值和 null 标志。
 *
 * 目前这不处理 PLpgSQL_expr 数据项；这不是必要的
 * 因为我们从未将对这些数据项的引用传递给 SPI。
 *
 * 注意：在通过引用传递的数据类型的情况下，返回的 Datum 正好指向
 * 存储值。通常，调用者应小心不要修改
 * 存储的值。不过，一些调用者故意操作
 * 由 R/W 扩展指针引用的变量；这些调用者
 * 有责任确保结果在语义上是正确的。
 *
 * 在某些情况下，我们必须为返回值进行 palloc，在这种情况下，我们将其放入
 * estate 的 eval_mcontext 中。
 */
static void fc_exec_eval_datum(PLpgSQL_execstate *fc_estate,
				PLpgSQL_datum *fc_datum,
				Oid *fc_typeid,
				int32 *fc_typetypmod,
				Datum *fc_value,
				bool *fc_isnull)
{
	MemoryContext fc_oldcontext;

	switch (fc_datum->dtype)
	{
		case PLPGSQL_DTYPE_PROMISE:
			/* 如有需要，则履行承诺，然后像普通变量一样处理 */
			fc_plpgsql_fulfill_promise(fc_estate, (PLpgSQL_var *) fc_datum);

			/* FALL THRU */

		case PLPGSQL_DTYPE_VAR:
			{
				PLpgSQL_var *fc_var = (PLpgSQL_var *) fc_datum;

				*fc_typeid = fc_var->datatype->typoid;
				*fc_typetypmod = fc_var->datatype->atttypmod;
				*fc_value = fc_var->value;
				*fc_isnull = fc_var->isnull;
				break;
			}

		case PLPGSQL_DTYPE_ROW:
			{
				PLpgSQL_row *fc_row = (PLpgSQL_row *) fc_datum;
				HeapTuple	fc_tup;

				/* 如果有多个 OUT 参数，我们会到达这里 */
				if (!fc_row->rowtupdesc)	/* 不应该发生 */
					elog(ERROR, "row variable has no tupdesc");
				/* 确保我们有有效的类型/typmod 设置 */
				BlessTupleDesc(fc_row->rowtupdesc);
				fc_oldcontext = MemoryContextSwitchTo(get_eval_mcontext(fc_estate));
				fc_tup = fc_make_tuple_from_row(fc_estate, fc_row, fc_row->rowtupdesc);
				if (fc_tup == NULL)	/* 不应该发生 */
					elog(ERROR, "row not compatible with its own tupdesc");
				*fc_typeid = fc_row->rowtupdesc->tdtypeid;
				*fc_typetypmod = fc_row->rowtupdesc->tdtypmod;
				*fc_value = HeapTupleGetDatum(fc_tup);
				*fc_isnull = false;
				MemoryContextSwitchTo(fc_oldcontext);
				break;
			}

		case PLPGSQL_DTYPE_REC:
			{
				PLpgSQL_rec *fc_rec = (PLpgSQL_rec *) fc_datum;

				if (fc_rec->erh == NULL)
				{
					/* 将未实例化的记录视为简单的 NULL */
					*fc_value = (Datum) 0;
					*fc_isnull = true;
					/* 报告变量的声明类型 */
					*fc_typeid = fc_rec->rectypeid;
					*fc_typetypmod = -1;
				}
				else
				{
					if (ExpandedRecordIsEmpty(fc_rec->erh))
					{
						/* 空记录也是 NULL */
						*fc_value = (Datum) 0;
						*fc_isnull = true;
					}
					else
					{
						*fc_value = ExpandedRecordGetDatum(fc_rec->erh);
						*fc_isnull = false;
					}
					if (fc_rec->rectypeid != RECORDOID)
					{
						/* 如果不是 RECORD，则报告变量的声明类型 */
						*fc_typeid = fc_rec->rectypeid;
						*fc_typetypmod = -1;
					}
					else
					{
						/* 如果声明为 RECORD，则报告记录的实际类型 */
						*fc_typeid = fc_rec->erh->er_typeid;
						*fc_typetypmod = fc_rec->erh->er_typmod;
					}
				}
				break;
			}

		case PLPGSQL_DTYPE_RECFIELD:
			{
				PLpgSQL_recfield *fc_recfield = (PLpgSQL_recfield *) fc_datum;
				PLpgSQL_rec *fc_rec;
				ExpandedRecordHeader *fc_erh;

				fc_rec = (PLpgSQL_rec *) (fc_estate->datums[fc_recfield->recparentno]);
				fc_erh = fc_rec->erh;

				/*
				 * 如果记录变量为 NULL，如果它有一个
				 * 命名的复合类型，则实例化它，否则进行抱怨。 （这不会改变
				 * 记录的逻辑状态：它仍然是 NULL。）
				 */
				if (fc_erh == NULL)
				{
					fc_instantiate_empty_record_variable(fc_estate, fc_rec);
					fc_erh = fc_rec->erh;
				}

				/*
				 * 如果我们尚未查找字段的属性，或者
				 * 如果元组描述符 ID 自上次以来发生变化。
				 */
				if (unlikely(fc_recfield->rectupledescid != fc_erh->er_tupdesc_id))
				{
					if (!expanded_record_lookup_field(fc_erh,
													  fc_recfield->fieldname,
													  &fc_recfield->finfo))
						ereport(ERROR,
								(errcode(ERRCODE_UNDEFINED_COLUMN),
								 errmsg("record \"%s\" has no field \"%s\"",
										fc_rec->refname, fc_recfield->fieldname)));
					fc_recfield->rectupledescid = fc_erh->er_tupdesc_id;
				}

				/* 报告类型数据。 */
				*fc_typeid = fc_recfield->finfo.ftypeid;
				*fc_typetypmod = fc_recfield->finfo.ftypmod;

				/* 并获取字段值。 */
				*fc_value = expanded_record_get_field(fc_erh,
												   fc_recfield->finfo.fnumber,
												   fc_isnull);
				break;
			}

		default:
			elog(ERROR, "unrecognized dtype: %d", fc_datum->dtype);
	}
}

/*
 * plpgsql_exec_get_datum_type				获取 PLpgSQL_datum 的数据类型
 *
 * 这与 exec_eval_datum 中的逻辑相同，但我们跳过获取
 * 变量的实际值。此外，也不需要支持 DTYPE_ROW。
 */
Oid plpgsql_exec_get_datum_type(PLpgSQL_execstate *fc_estate,
							PLpgSQL_datum *fc_datum)
{
	Oid			fc_typeid;

	switch (fc_datum->dtype)
	{
		case PLPGSQL_DTYPE_VAR:
		case PLPGSQL_DTYPE_PROMISE:
			{
				PLpgSQL_var *fc_var = (PLpgSQL_var *) fc_datum;

				fc_typeid = fc_var->datatype->typoid;
				break;
			}

		case PLPGSQL_DTYPE_REC:
			{
				PLpgSQL_rec *fc_rec = (PLpgSQL_rec *) fc_datum;

				if (fc_rec->erh == NULL || fc_rec->rectypeid != RECORDOID)
				{
					/* 报告变量的声明类型 */
					fc_typeid = fc_rec->rectypeid;
				}
				else
				{
					/* 如果声明为 RECORD，则报告记录的实际类型 */
					fc_typeid = fc_rec->erh->er_typeid;
				}
				break;
			}

		case PLPGSQL_DTYPE_RECFIELD:
			{
				PLpgSQL_recfield *fc_recfield = (PLpgSQL_recfield *) fc_datum;
				PLpgSQL_rec *fc_rec;

				fc_rec = (PLpgSQL_rec *) (fc_estate->datums[fc_recfield->recparentno]);

				/*
				 * 如果记录变量为 NULL，如果它有一个
				 * 命名的复合类型，则实例化它，否则进行抱怨。 （这不会改变
				 * 记录的逻辑状态：它仍然是 NULL。）
				 */
				if (fc_rec->erh == NULL)
					fc_instantiate_empty_record_variable(fc_estate, fc_rec);

				/*
				 * 如果我们尚未查找字段的属性，或者
				 * 如果元组描述符 ID 自上次以来发生变化。
				 */
				if (unlikely(fc_recfield->rectupledescid != fc_rec->erh->er_tupdesc_id))
				{
					if (!expanded_record_lookup_field(fc_rec->erh,
													  fc_recfield->fieldname,
													  &fc_recfield->finfo))
						ereport(ERROR,
								(errcode(ERRCODE_UNDEFINED_COLUMN),
								 errmsg("record \"%s\" has no field \"%s\"",
										fc_rec->refname, fc_recfield->fieldname)));
					fc_recfield->rectupledescid = fc_rec->erh->er_tupdesc_id;
				}

				fc_typeid = fc_recfield->finfo.ftypeid;
				break;
			}

		default:
			elog(ERROR, "unrecognized dtype: %d", fc_datum->dtype);
			fc_typeid = InvalidOid;	/* 保持编译器安静 */
			break;
	}

	return fc_typeid;
}

/*
 * plpgsql_exec_get_datum_type_info			获取 PLpgSQL_datum 的数据类型等
 *
 * plpgsql_exec_get_datum_type 的扩展版本，它还检索该数据的 typmod 和 collation。不过请注意，我们不会报告 RECORD 值的可能可变 typmod，而是始终返回 -1。
 */
void plpgsql_exec_get_datum_type_info(PLpgSQL_execstate *fc_estate,
								 PLpgSQL_datum *fc_datum,
								 Oid *fc_typeId, int32 *fc_typMod, Oid *fc_collation)
{
	switch (fc_datum->dtype)
	{
		case PLPGSQL_DTYPE_VAR:
		case PLPGSQL_DTYPE_PROMISE:
			{
				PLpgSQL_var *fc_var = (PLpgSQL_var *) fc_datum;

				*fc_typeId = fc_var->datatype->typoid;
				*fc_typMod = fc_var->datatype->atttypmod;
				*fc_collation = fc_var->datatype->collation;
				break;
			}

		case PLPGSQL_DTYPE_REC:
			{
				PLpgSQL_rec *fc_rec = (PLpgSQL_rec *) fc_datum;

				if (fc_rec->erh == NULL || fc_rec->rectypeid != RECORDOID)
				{
					/* 报告变量的声明类型 */
					*fc_typeId = fc_rec->rectypeid;
					*fc_typMod = -1;
				}
				else
				{
					/* 如果声明为 RECORD，则报告记录的实际类型 */
					*fc_typeId = fc_rec->erh->er_typeid;
					/* 不要返回 RECORD 变量的可变 typmod */
					*fc_typMod = -1;
				}
				/* 复合类型是永远不能比较的 */
				*fc_collation = InvalidOid;
				break;
			}

		case PLPGSQL_DTYPE_RECFIELD:
			{
				PLpgSQL_recfield *fc_recfield = (PLpgSQL_recfield *) fc_datum;
				PLpgSQL_rec *fc_rec;

				fc_rec = (PLpgSQL_rec *) (fc_estate->datums[fc_recfield->recparentno]);

				/*
				 * 如果记录变量为 NULL，如果它有一个
				 * 命名的复合类型，则实例化它，否则进行抱怨。 （这不会改变
				 * 记录的逻辑状态：它仍然是 NULL。）
				 */
				if (fc_rec->erh == NULL)
					fc_instantiate_empty_record_variable(fc_estate, fc_rec);

				/*
				 * 如果我们尚未查找字段的属性，或者
				 * 如果元组描述符 ID 自上次以来发生变化。
				 */
				if (unlikely(fc_recfield->rectupledescid != fc_rec->erh->er_tupdesc_id))
				{
					if (!expanded_record_lookup_field(fc_rec->erh,
													  fc_recfield->fieldname,
													  &fc_recfield->finfo))
						ereport(ERROR,
								(errcode(ERRCODE_UNDEFINED_COLUMN),
								 errmsg("record \"%s\" has no field \"%s\"",
										fc_rec->refname, fc_recfield->fieldname)));
					fc_recfield->rectupledescid = fc_rec->erh->er_tupdesc_id;
				}

				*fc_typeId = fc_recfield->finfo.ftypeid;
				*fc_typMod = fc_recfield->finfo.ftypmod;
				*fc_collation = fc_recfield->finfo.fcollation;
				break;
			}

		default:
			elog(ERROR, "unrecognized dtype: %d", fc_datum->dtype);
			*fc_typeId = InvalidOid;	/* 保持编译器安静 */
			*fc_typMod = -1;
			*fc_collation = InvalidOid;
			break;
	}
}

/* ----------
 * exec_eval_integer		评估一个表达式，将结果强制为 int4
 *
 * 请注意，我们在这里不执行 exec_eval_cleanup；调用者必须在稍后的某个时刻进行。
 * （我们这样做是因为调用者可能持有其他按引用传递的表达式评估的结果，例如一个将被下标的数组值。）
 * ----------
 */
static int fc_exec_eval_integer(PLpgSQL_execstate *fc_estate,
				  PLpgSQL_expr *fc_expr,
				  bool *fc_isNull)
{
	Datum		fc_exprdatum;
	Oid			fc_exprtypeid;
	int32		fc_exprtypmod;

	fc_exprdatum = fc_exec_eval_expr(fc_estate, fc_expr, fc_isNull, &fc_exprtypeid, &fc_exprtypmod);
	fc_exprdatum = fc_exec_cast_value(fc_estate, fc_exprdatum, fc_isNull,
								fc_exprtypeid, fc_exprtypmod,
								INT4OID, -1);
	return DatumGetInt32(fc_exprdatum);
}

/* ----------
 * exec_eval_boolean		评估一个表达式，将结果强制为 bool
 *
 * 请注意，我们在这里不执行 exec_eval_cleanup；调用者必须在稍后的某个时刻进行。
 * ----------
 */
static bool fc_exec_eval_boolean(PLpgSQL_execstate *fc_estate,
				  PLpgSQL_expr *fc_expr,
				  bool *fc_isNull)
{
	Datum		fc_exprdatum;
	Oid			fc_exprtypeid;
	int32		fc_exprtypmod;

	fc_exprdatum = fc_exec_eval_expr(fc_estate, fc_expr, fc_isNull, &fc_exprtypeid, &fc_exprtypmod);
	fc_exprdatum = fc_exec_cast_value(fc_estate, fc_exprdatum, fc_isNull,
								fc_exprtypeid, fc_exprtypmod,
								BOOLOID, -1);
	return DatumGetBool(fc_exprdatum);
}

/* ----------
 * exec_eval_expr			评估一个表达式并返回
 *					结果 Datum，以及数据类型/typmod。
 *
 * 注意：调用者在处理完 Datum 后必须执行 exec_eval_cleanup。
 * ----------
 */
static Datum fc_exec_eval_expr(PLpgSQL_execstate *fc_estate,
			   PLpgSQL_expr *fc_expr,
			   bool *fc_isNull,
			   Oid *fc_rettype,
			   int32 *fc_rettypmod)
{
	Datum		fc_result = 0;
	int			fc_rc;
	Form_pg_attribute fc_attr;

	/*
	 * 如果是第一次执行，创建此表达式的计划。
	 */
	if (fc_expr->plan == NULL)
		fc_exec_prepare_plan(fc_estate, fc_expr, CURSOR_OPT_PARALLEL_OK);

	/*
	 * 如果这是一个简单的表达式，绕过 SPI 直接使用执行器
	 */
	if (fc_exec_eval_simple_expr(fc_estate, fc_expr,
							  &fc_result, fc_isNull, fc_rettype, fc_rettypmod))
		return fc_result;

	/*
	 * 否则通过 exec_run_select 以困难的方式进行
	 */
	fc_rc = fc_exec_run_select(fc_estate, fc_expr, 2, NULL);
	if (fc_rc != SPI_OK_SELECT)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("query did not return data"),
				 errcontext("query: %s", fc_expr->query)));

	/*
	 * 检查表达式是否确切返回一列...
	 */
	if (fc_estate->eval_tuptable->tupdesc->natts != 1)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg_plural("query returned %d column",
							   "query returned %d columns",
							   fc_estate->eval_tuptable->tupdesc->natts,
							   fc_estate->eval_tuptable->tupdesc->natts),
				 errcontext("query: %s", fc_expr->query)));

	/*
	 * ...并获取该列的数据类型。
	 */
	fc_attr = TupleDescAttr(fc_estate->eval_tuptable->tupdesc, 0);
	*fc_rettype = fc_attr->atttypid;
	*fc_rettypmod = fc_attr->atttypmod;

	/*
	 * 如果没有选择行，则结果为该类型的 NULL。
	 */
	if (fc_estate->eval_processed == 0)
	{
		*fc_isNull = true;
		return (Datum) 0;
	}

	/*
	 * 检查表达式返回的行数是否不超过一行。
	 */
	if (fc_estate->eval_processed != 1)
		ereport(ERROR,
				(errcode(ERRCODE_CARDINALITY_VIOLATION),
				 errmsg("query returned more than one row"),
				 errcontext("query: %s", fc_expr->query)));

	/*
	 * 返回单个结果 Datum。
	 */
	return SPI_getbinval(fc_estate->eval_tuptable->vals[0],
						 fc_estate->eval_tuptable->tupdesc, 1, fc_isNull);
}


/* ----------
 * exec_run_select			执行一个选择查询
 * ----------
 */
static int fc_exec_run_select(PLpgSQL_execstate *fc_estate,
				PLpgSQL_expr *fc_expr, long fc_maxtuples, Portal *fc_portalP)
{
	ParamListInfo fc_paramLI;
	int			fc_rc;

	/*
	 * 在第一次调用此表达式时生成计划。
	 *
	 * 如果我们不需要返回一个 portal，那么我们将立即执行查询，这意味着即使获取的行数有限，也可以使用并行计划。如果我们确实需要返回一个 portal（即这是为了 FOR 循环），用户的代码可能会在 FOR 循环内部调用额外的操作，从而使并行查询不安全。在任何情况下，我们都不希望进行任何游标操作，因此指定 NO_SCROLL 以提高效率和语义安全性。
	 */
	if (fc_expr->plan == NULL)
	{
		int			fc_cursorOptions = CURSOR_OPT_NO_SCROLL;

		if (fc_portalP == NULL)
			fc_cursorOptions |= CURSOR_OPT_PARALLEL_OK;
		fc_exec_prepare_plan(fc_estate, fc_expr, fc_cursorOptions);
	}

	/*
	 * 设置 ParamListInfo 以传递给执行器
	 */
	fc_paramLI = fc_setup_param_list(fc_estate, fc_expr);

	/*
	 * 如果请求了一个 portal，将查询和参数列表放入 portal
	 */
	if (fc_portalP != NULL)
	{
		*fc_portalP = SPI_cursor_open_with_paramlist(NULL, fc_expr->plan,
												  fc_paramLI,
												  fc_estate->readonly_func);
		if (*fc_portalP == NULL)
			elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
				 fc_expr->query, SPI_result_code_string(SPI_result));
		fc_exec_eval_cleanup(fc_estate);
		return SPI_OK_CURSOR;
	}

	/*
	 * 执行查询
	 */
	fc_rc = SPI_execute_plan_with_paramlist(fc_expr->plan, fc_paramLI,
										 fc_estate->readonly_func, fc_maxtuples);
	if (fc_rc != SPI_OK_SELECT)
	{
		/*
		 * SELECT INTO 值得一个特殊的错误消息，因为“查询不是一个 SELECT”在这种情况下不是很有帮助。
		 */
		if (fc_rc == SPI_OK_SELINTO)
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("query is SELECT INTO, but it should be plain SELECT"),
					 errcontext("query: %s", fc_expr->query)));
		else
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("query is not a SELECT"),
					 errcontext("query: %s", fc_expr->query)));
	}

	/* 保存查询结果以便最终清理 */
	Assert(fc_estate->eval_tuptable == NULL);
	fc_estate->eval_tuptable = SPI_tuptable;
	fc_estate->eval_processed = SPI_processed;

	return fc_rc;
}


/*
 * exec_for_query --- 为来自 portal 的每一行执行 FOR 循环的主体
 *
 * 被 exec_stmt_fors、exec_stmt_forc 和 exec_stmt_dynfors 使用
 */
static int fc_exec_for_query(PLpgSQL_execstate *fc_estate, PLpgSQL_stmt_forq *fc_stmt,
			   Portal fc_portal, bool fc_prefetch_ok)
{
	PLpgSQL_variable *fc_var;
	SPITupleTable *fc_tuptab;
	bool		fc_found = false;
	int			fc_rc = PLPGSQL_RC_OK;
	uint64		fc_previous_id = INVALID_TUPLEDESC_IDENTIFIER;
	bool		fc_tupdescs_match = true;
	uint64		fc_n;

	/* 获取循环变量的数据条目 */
	fc_var = (PLpgSQL_variable *) fc_estate->datums[fc_stmt->var->dno];

	/*
	 * 确保 portal 不会被我们执行的用户语句关闭。
	 */
	PinPortal(fc_portal);

	/*
	 * 在非原子上下文中，我们不敢预取，即使这在其他情况下是安全的。除了可能产生的任何语义危险之外，如果我们预取了 toast 数据，然后用户提交事务，toast 引用可能会变成悬空指针。（我们尚未从游标中获取的行是安全的，因为 PersistHoldablePortal 机制处理这种情况。）
	 */
	if (!fc_estate->atomic)
		fc_prefetch_ok = false;

	/*
	 * 获取初始元组。如果允许预取，那么我们会抓取更多的行以避免多次通过执行器启动的开销。
	 */
	SPI_cursor_fetch(fc_portal, true, fc_prefetch_ok ? 10 : 1);
	fc_tuptab = SPI_tuptable;
	fc_n = SPI_processed;

	/*
	 * 如果查询没有返回任何行，将目标设置为 NULL，并通过 found = false 继续。
	 */
	if (fc_n == 0)
	{
		fc_exec_move_row(fc_estate, fc_var, NULL, fc_tuptab->tupdesc);
		fc_exec_eval_cleanup(fc_estate);
	}
	else
		fc_found = true;			/* 至少处理了一个元组 */

	/*
	 * 现在执行循环
	 */
	while (fc_n > 0)
	{
		uint64		fc_i;

		for (fc_i = 0; fc_i < fc_n; fc_i++)
		{
			/*
			 * 将元组分配给目标。在这里，因为我们知道所有循环迭代应该分配相同的 tupdesc，我们可以优化掉重复创建具有相同 tupdescs 的展开记录。测试 er_tupdesc_id 的变化是可靠的，即使循环主体包含完全替换目标值的赋值，因为它是从进程全局计数器中分配的。tupdescs 不匹配的情况可能比这种编码处理得更有效，但并不清楚额外的努力是否值得。
			 */
			if (fc_var->dtype == PLPGSQL_DTYPE_REC)
			{
				PLpgSQL_rec *fc_rec = (PLpgSQL_rec *) fc_var;

				if (fc_rec->erh &&
					fc_rec->erh->er_tupdesc_id == fc_previous_id &&
					fc_tupdescs_match)
				{
					/* 只需分配一个新的元组值 */
					expanded_record_set_tuple(fc_rec->erh, fc_tuptab->vals[fc_i],
											  true, !fc_estate->atomic);
				}
				else
				{
					/*
					 * 第一次通过，或循环中变量的 tupdesc 发生改变，或我们必须以困难的方式进行，因为需要类型转换。
					 */
					fc_exec_move_row(fc_estate, fc_var,
								  fc_tuptab->vals[fc_i], fc_tuptab->tupdesc);

					/*
					 * 检查下一次物理分配是否可以。
					 * 一旦 tupdesc 比较失败，我们就不再
					 * 在后续的循环迭代中重新检查。
					 */
					if (fc_tupdescs_match)
					{
						fc_tupdescs_match =
							(fc_rec->rectypeid == RECORDOID ||
							 fc_rec->rectypeid == fc_tuptab->tupdesc->tdtypeid ||
							 fc_compatible_tupdescs(fc_tuptab->tupdesc,
												 expanded_record_get_tupdesc(fc_rec->erh)));
					}
					fc_previous_id = fc_rec->erh->er_tupdesc_id;
				}
			}
			else
				fc_exec_move_row(fc_estate, fc_var, fc_tuptab->vals[fc_i], fc_tuptab->tupdesc);

			fc_exec_eval_cleanup(fc_estate);

			/*
			 * 执行语句
			 */
			fc_rc = fc_exec_stmts(fc_estate, fc_stmt->body);

			LOOP_RC_PROCESSING(fc_stmt->label, goto loop_exit);
		}

		SPI_freetuptable(fc_tuptab);

		/*
		 * 获取更多元组。如果允许预取，则每次获取 50 个。
		 */
		SPI_cursor_fetch(fc_portal, true, fc_prefetch_ok ? 50 : 1);
		fc_tuptab = SPI_tuptable;
		fc_n = SPI_processed;
	}

loop_exit:

	/*
	 * 释放最后一组元组（如果有的话）
	 */
	SPI_freetuptable(fc_tuptab);

	UnpinPortal(fc_portal);

	/*
	 * 设置 FOUND 变量以指示循环执行的结果
	 * （即，我们是否循环了一次或多次）。这必须最后设置，以
	 * 便它不会干扰循环处理中的 FOUND 变量的值。
	 */
	fc_exec_set_found(fc_estate, fc_found);

	return fc_rc;
}


/* ----------
 * exec_eval_simple_expr -  直接调用 ExecEvalExpr() 评估一个简单的表达式并返回
 *                             一个 Datum。
 *
 * 如果成功，将结果存储到 *result、*isNull、*rettype、*rettypmod
 * 中并返回 true。如果表达式无法通过简单评估处理，
 * 则返回 false。
 *
 * 因为我们只为简单表达式存储一个执行树，我们
 * 无法处理递归情况。因此，如果我们看到树在当前事务中
 * 正在忙于评估，我们只需返回 false，让调用者以更复杂的方式运行表达式。
 * （诸如为递归调用创建新树的其他替代方案要么引入内存泄漏，
 * 要么增加足够的业务记录而令人怀疑胜利。）另一个通过 expr_simple_in_use 测试
 * 覆盖的情况是之前的执行因错误而被中止：树可能包含错误状态，
 * 所以我们不敢重新使用它。
 *
 * 有可能我们需要重新规划一个简单表达式；例如，
 * 有人可能重新定义了一个曾被内联到简单表达式中的 SQL 函数。
 * 这不会导致简单表达式变得非简单（或反之），但我们确实需要处理替换表达式树。
 *
 * 注意：如果是引用传递，结果在 eval_mcontext 中。
 * 它将在 exec_eval_cleanup 完成时被释放。
 * ----------
 */
static bool fc_exec_eval_simple_expr(PLpgSQL_execstate *fc_estate,
					  PLpgSQL_expr *fc_expr,
					  Datum *fc_result,
					  bool *fc_isNull,
					  Oid *fc_rettype,
					  int32 *fc_rettypmod)
{
	ExprContext *fc_econtext = fc_estate->eval_econtext;
	LocalTransactionId fc_curlxid = MyProc->lxid;
	ParamListInfo fc_paramLI;
	void	   *fc_save_setup_arg;
	bool		fc_need_snapshot;
	MemoryContext fc_oldcontext;

	/*
	 * 如果表达式之前不是简单的，则忘记它。
	 */
	if (fc_expr->expr_simple_expr == NULL)
		return false;

	/*
	 * 如果表达式在当前事务中正在使用，则不要触碰它。
	 */
	if (unlikely(fc_expr->expr_simple_in_use) &&
		fc_expr->expr_simple_lxid == fc_curlxid)
		return false;

	/*
	 * 确保有一个门户级快照，以防这个简单
	 * 表达式是在 COMMIT 或 ROLLBACK 后评估的第一件事。
	 * 在执行表达式之前我们无论如何都要这样做，因此我们
	 * 不如现在就这样做，以确保任何可能的重新规划不
	 * 需要再次获取快照。
	 */
	EnsurePortalSnapshotExists();

	/*
	 * 检查缓存的计划是否已失效。如果没有，并且这是
	 * 当前事务中的第一次使用，请在简单表达式的 resowner 中保存一个计划引用计数。
	 */
	if (likely(CachedPlanIsSimplyValid(fc_expr->expr_simple_plansource,
									   fc_expr->expr_simple_plan,
									   (fc_expr->expr_simple_plan_lxid != fc_curlxid ?
										fc_estate->simple_eval_resowner : NULL))))
	{
		/*
		 * 它仍然有效，所以只需记住我们在当前事务中对
		 * 计划有一个引用计数。（如果我们已经有一个，这个
		 * 赋值是无操作。）
		 */
		fc_expr->expr_simple_plan_lxid = fc_curlxid;
	}
	else
	{
		/* 需要重新规划 */
		CachedPlan *fc_cplan;

		/*
		 * 如果我们对某个以前版本的计划有有效的引用计数，
		 * 释放它，以免在事务内泄露计划。
		 */
		if (fc_expr->expr_simple_plan_lxid == fc_curlxid)
			ReleaseCachedPlan(fc_expr->expr_simple_plan,
							  fc_estate->simple_eval_resowner);

		/*
		 * 重置为“非简单”以保持合理状态（没有悬空
		 * 指针），以防我们在重新规划时失败。expr_simple_plansource
		 * 可以保持不变，因为它不能移动。
		 */
		fc_expr->expr_simple_expr = NULL;
		fc_expr->expr_rw_param = NULL;
		fc_expr->expr_simple_plan = NULL;
		fc_expr->expr_simple_plan_lxid = InvalidLocalTransactionId;

		/* 在 eval_mcontext 中进行重新规划工作 */
		fc_oldcontext = MemoryContextSwitchTo(get_eval_mcontext(fc_estate));
		fc_cplan = SPI_plan_get_cached_plan(fc_expr->plan);
		MemoryContextSwitchTo(fc_oldcontext);

		/*
		 * 在这里我们不能出现失败，因为 SPI 计划中的 CachedPlanSources
		 * 的数量不能改变 exec_simple_check_plan 所看到的数量；
		 * 这是从查询文本生成的原始解析树的一个属性。
		 */
		Assert(fc_cplan != NULL);

		/*
		 * 重新检查 exec_is_simple_query，在某些极端情况下可能会
		 * 报告错误，比如非 SRF 被 SRF 替换。也重新检查
		 * CachedPlanAllowsSimpleValidityCheck，以确保。
		 * 如果任一测试失败，则通过声明该计划为非简单
		 * 来处理。成功时，我们将对存储在 simple_eval_resowner 中的新计划
		 * 获取一个引用计数。
		 */
		if (fc_exec_is_simple_query(fc_expr) &&
			CachedPlanAllowsSimpleValidityCheck(fc_expr->expr_simple_plansource,
												fc_cplan,
												fc_estate->simple_eval_resowner))
		{
			/* 记住我们有引用计数 */
			fc_expr->expr_simple_plan = fc_cplan;
			fc_expr->expr_simple_plan_lxid = fc_curlxid;
		}
		else
		{
			/* 释放 SPI_plan_get_cached_plan 的引用计数 */
			ReleaseCachedPlan(fc_cplan, CurrentResourceOwner);
			return false;
		}

		/*
		 * SPI_plan_get_cached_plan 获取了一个存储在
		 * 活动资源所有者中的计划引用计数。我们不再需要
		 * 它，因此释放它。
		 */
		ReleaseCachedPlan(fc_cplan, CurrentResourceOwner);

		/* 从缓存的计划中提取所需的标量表达式 */
		fc_exec_save_simple_expr(fc_expr, fc_cplan);
	}

	/*
	 * 返回之前确定的结果类型。
	 */
	*fc_rettype = fc_expr->expr_simple_type;
	*fc_rettypmod = fc_expr->expr_simple_typmod;

	/*
	 * 设置 ParamListInfo 以传递给执行器。为了安全起见，在我们
	 * 使用参数列表时保存并恢复 estate->paramLI->parserSetupArg。
	 */
	fc_paramLI = fc_estate->paramLI;
	fc_save_setup_arg = fc_paramLI->parserSetupArg;

	/*
	 * 我们可以跳过使用 setup_param_list()，而只是无条件执行
	 * 这一操作，因为没有必要优化可能将 ecxt_param_list_info 
	 * 设置为 NULL；我们已经强制使用了一个通用计划。
	 */
	fc_paramLI->parserSetupArg = (void *) fc_expr;
	fc_econtext->ecxt_param_list_info = fc_paramLI;

	/*
	 * 准备表达式以进行执行，如果在当前事务中尚未执行。
	 * （如果我们在上面调用了 exec_save_simple_expr，这将被强制发生。）
	 */
	if (unlikely(fc_expr->expr_simple_lxid != fc_curlxid))
	{
		fc_oldcontext = MemoryContextSwitchTo(fc_estate->simple_eval_estate->es_query_cxt);
		fc_expr->expr_simple_state =
			ExecInitExprWithParams(fc_expr->expr_simple_expr,
								   fc_econtext->ecxt_param_list_info);
		fc_expr->expr_simple_in_use = false;
		fc_expr->expr_simple_lxid = fc_curlxid;
		MemoryContextSwitchTo(fc_oldcontext);
	}

	/*
	 * 我们必须执行一些 SPI_execute_plan 会做的事情，
	 * 特别是推送一个新的快照，以便表达式中的稳定函数
	 * 可以看到到目前为止我们自己的函数所做的更新。
	 * 然而，在某些情况下，我们可以跳过这一操作
	 * （只需使用传递给我们函数的相同快照调用表达式），
	 * 这很有用，因为相对于简单表达式的成本来说
	 * 它是相当昂贵的。 如果表达式不包含任何稳定或易变函数，
	 * 我们可以跳过它；不变函数不需要看到我们的更新。
	 * 另外，如果这是一个只读函数，我们没有进行任何更新，
	 * 所以再次跳过是可以的。
	 */
	fc_oldcontext = MemoryContextSwitchTo(get_eval_mcontext(fc_estate));
	fc_need_snapshot = (fc_expr->expr_simple_mutable && !fc_estate->readonly_func);
	if (fc_need_snapshot)
	{
		CommandCounterIncrement();
		PushActiveSnapshot(GetTransactionSnapshot());
	}

	/*
	 * 在 ExecEvalExpr 调用期间将表达式标记为繁忙。
	 */
	fc_expr->expr_simple_in_use = true;

	/*
	 * 最后我们可以调用执行器来评估表达式
	 */
	*fc_result = ExecEvalExpr(fc_expr->expr_simple_state,
						   fc_econtext,
						   fc_isNull);

	/* 各种清理工作 */
	fc_expr->expr_simple_in_use = false;

	fc_econtext->ecxt_param_list_info = NULL;

	fc_paramLI->parserSetupArg = fc_save_setup_arg;

	if (fc_need_snapshot)
		PopActiveSnapshot();

	MemoryContextSwitchTo(fc_oldcontext);

	/*
	 * 就这样。
	 */
	return true;
}


/*
 * 创建一个 ParamListInfo 以传递给 SPI
 *
 * 我们在评估此 estate 中的 PLpgSQL_exprs 时为所有 SPI 调用使用一个单一的 ParamListInfo 结构。它不包含每个参数的数据，仅包含挂钩函数，因此对于 SPI 来说，它实际上是只读的。
 *
 * 纯粹只读的一个例外是 parserSetupArg 指向正在评估的特定 PLpgSQL_expr。这对于语句级调用者来说不是问题，但低级调用者必须保存和恢复 estate->paramLI->parserSetupArg，以防在外部调用级别存在活动评估。（一种合理的替代设计是为每个 PLpgSQL_expr 创建一个 ParamListInfo 结构，但目前看来这似乎是浪费内存。）
 */
static ParamListInfo fc_setup_param_list(PLpgSQL_execstate *fc_estate, PLpgSQL_expr *fc_expr)
{
	ParamListInfo fc_paramLI;

	/*
	 * 我们必须已经创建了 SPIPlan（因此，查询文本至少已经被解析/分析过一次）；否则我们不能依赖 expr->paramnos。
	 */
	Assert(fc_expr->plan != NULL);

	/*
	 * 只有在表达式具有参数时，我们才需要 ParamListInfo。从原则上讲，我们应该使用 bms_is_empty() 来测试，但我们使用非空测试，因为它更快。在当前的使用中，位从 expr->paramnos 中永远不会被移除，只会被添加，因此这个测试在任何情况下都是正确的。
	 */
	if (fc_expr->paramnos)
	{
		/* 使用公共的 ParamListInfo */
		fc_paramLI = fc_estate->paramLI;

		/*
		 * 设置指向活动 expr 的链接，以便挂钩函数可以找到它。如果有任何可能中断参数的活动使用，调用者必须保存和恢复 parserSetupArg。
		 */
		fc_paramLI->parserSetupArg = (void *) fc_expr;

		/*
		 * 确保在解析器挂钩需要它之前设置此值。没有必要保存和恢复，因为设置后值始终是正确的。（应该已经设置，但让我们确保。）
		 */
		fc_expr->func = fc_estate->func;
	}
	else
	{
		/*
		 * 表达式不需要参数。确保我们将此情况表示为 NULL ParamListInfo，以便 plancache.c 知道定制计划没有意义。
		 */
		fc_paramLI = NULL;
	}
	return fc_paramLI;
}

/*
 * plpgsql_param_fetch		paramFetch 回调用于动态参数获取
 *
 * 我们始终使用调用者的工作区来构造返回的结构。
 *
 * 注意：在查询执行期间不再使用此项。它在规划期间使用（当 speculative == true）以及当我们提供给执行者的 ParamListInfo 被复制到游标门户或转移到并行子进程时使用。
 */
static ParamExternData *
fc_plpgsql_param_fetch(ParamListInfo fc_params,
					int fc_paramid, bool fc_speculative,
					ParamExternData *fc_prm)
{
	int			fc_dno;
	PLpgSQL_execstate *fc_estate;
	PLpgSQL_expr *fc_expr;
	PLpgSQL_datum *fc_datum;
	bool		fc_ok = true;
	int32		fc_prmtypmod;

	/* paramid 是基于 1 的，但 dnos 基于 0 */
	fc_dno = fc_paramid - 1;
	Assert(fc_dno >= 0 && fc_dno < fc_params->numParams);

	/* 获取挂钩数据 */
	fc_estate = (PLpgSQL_execstate *) fc_params->paramFetchArg;
	fc_expr = (PLpgSQL_expr *) fc_params->parserSetupArg;
	Assert(fc_params->numParams == fc_estate->ndatums);

	/* 现在我们可以访问目标数据项 */
	fc_datum = fc_estate->datums[fc_dno];

	/*
	 * 由于 copyParamList() 或 SerializeParamList() 会尝试物化每个参数槽，因此在请求不应由此 SQL 表达式使用的数据项时，返回一个虚拟参数是很重要的。否则我们可能会在 exec_eval_datum() 中遇到失败，或者复制比必要的更多数据。
	 */
	if (!bms_is_member(fc_dno, fc_expr->paramnos))
		fc_ok = false;

	/*
	 * 如果访问是推测性的，我们更倾向于返回无数据，而不是在 exec_eval_datum() 中失败。检查可能的失败案例。
	 */
	else if (fc_speculative)
	{
		switch (fc_datum->dtype)
		{
			case PLPGSQL_DTYPE_VAR:
			case PLPGSQL_DTYPE_PROMISE:
				/* 始终安全 */
				break;

			case PLPGSQL_DTYPE_ROW:
				/* 在所有有趣的情况下都应该安全 */
				break;

			case PLPGSQL_DTYPE_REC:
				/* 始终安全（可能返回 NULL，这没关系） */
				break;

			case PLPGSQL_DTYPE_RECFIELD:
				{
					PLpgSQL_recfield *fc_recfield = (PLpgSQL_recfield *) fc_datum;
					PLpgSQL_rec *fc_rec;

					fc_rec = (PLpgSQL_rec *) (fc_estate->datums[fc_recfield->recparentno]);

					/*
					 * 如果记录变量为 NULL，则不冒任何风险。
					 */
					if (fc_rec->erh == NULL)
						fc_ok = false;

					/*
					 * 如果我们尚未查找字段的属性，或者如果元组描述符 ID 自上次以来发生了变化。
					 */
					else if (unlikely(fc_recfield->rectupledescid != fc_rec->erh->er_tupdesc_id))
					{
						if (expanded_record_lookup_field(fc_rec->erh,
														 fc_recfield->fieldname,
														 &fc_recfield->finfo))
							fc_recfield->rectupledescid = fc_rec->erh->er_tupdesc_id;
						else
							fc_ok = false;
					}
					break;
				}

			default:
				fc_ok = false;
				break;
		}
	}

	/* 如果不可以，则返回“没有这样的参数” */
	if (!fc_ok)
	{
		fc_prm->value = (Datum) 0;
		fc_prm->isnull = true;
		fc_prm->pflags = 0;
		fc_prm->ptype = InvalidOid;
		return fc_prm;
	}

	/* 好的，评估值并存储到返回结构中 */
	fc_exec_eval_datum(fc_estate, fc_datum,
					&fc_prm->ptype, &fc_prmtypmod,
					&fc_prm->value, &fc_prm->isnull);
	/* 我们可以始终将参数标记为“const”，以供执行器使用 */
	fc_prm->pflags = PARAM_FLAG_CONST;

	/*
	 * 如果它是一个读/写扩展数据项，将引用转换为只读。
	 * (鉴于此函数的使用场景，没有必要优化读/写参数。)
	 */
	if (fc_datum->dtype == PLPGSQL_DTYPE_VAR)
		fc_prm->value = MakeExpandedObjectReadOnly(fc_prm->value,
												fc_prm->isnull,
												((PLpgSQL_var *) fc_datum)->datatype->typlen);
	else if (fc_datum->dtype == PLPGSQL_DTYPE_REC)
		fc_prm->value = MakeExpandedObjectReadOnly(fc_prm->value,
												fc_prm->isnull,
												-1);

	return fc_prm;
}

/*
 * plpgsql_param_compile		plpgsql 参数的 paramCompile 回调
 */
static void fc_plpgsql_param_compile(ParamListInfo fc_params, Param *fc_param,
					  ExprState *fc_state,
					  Datum *fc_resv, bool *fc_resnull)
{
	PLpgSQL_execstate *fc_estate;
	PLpgSQL_expr *fc_expr;
	int			fc_dno;
	PLpgSQL_datum *fc_datum;
	ExprEvalStep fc_scratch;

	/* 获取挂钩数据 */
	fc_estate = (PLpgSQL_execstate *) fc_params->paramFetchArg;
	fc_expr = (PLpgSQL_expr *) fc_params->parserSetupArg;

	/* paramid 是基于 1 的，但 dnos 基于 0 */
	fc_dno = fc_param->paramid - 1;
	Assert(fc_dno >= 0 && fc_dno < fc_estate->ndatums);

	/* 现在我们可以访问目标数据项 */
	fc_datum = fc_estate->datums[fc_dno];

	fc_scratch.opcode = EEOP_PARAM_CALLBACK;
	fc_scratch.resvalue = fc_resv;
	fc_scratch.resnull = fc_resnull;

	/*
	 * 选择适当的评估函数。似乎有必要对 DTYPE_VAR 和 DTYPE_RECFIELD 进行特殊处理以提升性能。
	 * 此外，我们可以提前确定是否需要调用 MakeExpandedObjectReadOnly()。
	 * 目前，只有 VAR/PROMISE 和 REC 数据项可能包含读/写扩展对象。
	 */
	if (fc_datum->dtype == PLPGSQL_DTYPE_VAR)
	{
		if (fc_param != fc_expr->expr_rw_param &&
			((PLpgSQL_var *) fc_datum)->datatype->typlen == -1)
			fc_scratch.d.cparam.paramfunc = fc_plpgsql_param_eval_var_ro;
		else
			fc_scratch.d.cparam.paramfunc = fc_plpgsql_param_eval_var;
	}
	else if (fc_datum->dtype == PLPGSQL_DTYPE_RECFIELD)
		fc_scratch.d.cparam.paramfunc = fc_plpgsql_param_eval_recfield;
	else if (fc_datum->dtype == PLPGSQL_DTYPE_PROMISE)
	{
		if (fc_param != fc_expr->expr_rw_param &&
			((PLpgSQL_var *) fc_datum)->datatype->typlen == -1)
			fc_scratch.d.cparam.paramfunc = fc_plpgsql_param_eval_generic_ro;
		else
			fc_scratch.d.cparam.paramfunc = fc_plpgsql_param_eval_generic;
	}
	else if (fc_datum->dtype == PLPGSQL_DTYPE_REC &&
			 fc_param != fc_expr->expr_rw_param)
		fc_scratch.d.cparam.paramfunc = fc_plpgsql_param_eval_generic_ro;
	else
		fc_scratch.d.cparam.paramfunc = fc_plpgsql_param_eval_generic;

	/*
	 * 注意：使用 paramarg 来存储 estate 指针以节省评估函数中的间接调用是很诱人的。
	 * 但这行不通，因为编译的表达式可能与同一 PL/pgSQL 函数的不同 estate 一起使用。
	 */
	fc_scratch.d.cparam.paramarg = NULL;
	fc_scratch.d.cparam.paramid = fc_param->paramid;
	fc_scratch.d.cparam.paramtype = fc_param->paramtype;
	ExprEvalPushStep(fc_state, &fc_scratch);
}

/*
 * plpgsql_param_eval_var		EEOP_PARAM_CALLBACK 步骤的评估
 *
 * 这是针对 DTYPE_VAR 变量的特殊情况，我们不需要调用 MakeExpandedObjectReadOnly。
 */
static void fc_plpgsql_param_eval_var(ExprState *fc_state, ExprEvalStep *fc_op,
					   ExprContext *fc_econtext)
{
	ParamListInfo fc_params;
	PLpgSQL_execstate *fc_estate;
	int			fc_dno = fc_op->d.cparam.paramid - 1;
	PLpgSQL_var *fc_var;

	/* 获取挂钩数据 */
	fc_params = fc_econtext->ecxt_param_list_info;
	fc_estate = (PLpgSQL_execstate *) fc_params->paramFetchArg;
	Assert(fc_dno >= 0 && fc_dno < fc_estate->ndatums);

	/* 现在我们可以访问目标数据项 */
	fc_var = (PLpgSQL_var *) fc_estate->datums[fc_dno];
	Assert(fc_var->dtype == PLPGSQL_DTYPE_VAR);

	/* exec_eval_datum() 的内联版本 */
	*fc_op->resvalue = fc_var->value;
	*fc_op->resnull = fc_var->isnull;

	/* 安全检查 -- 断言应该足够 */
	Assert(fc_var->datatype->typoid == fc_op->d.cparam.paramtype);
}

/*
 * plpgsql_param_eval_var_ro		EEOP_PARAM_CALLBACK 步骤的评估
 *
 * 这是针对 DTYPE_VAR 变量的特殊情况，我们需要调用 MakeExpandedObjectReadOnly。
 */
static void fc_plpgsql_param_eval_var_ro(ExprState *fc_state, ExprEvalStep *fc_op,
						  ExprContext *fc_econtext)
{
	ParamListInfo fc_params;
	PLpgSQL_execstate *fc_estate;
	int			fc_dno = fc_op->d.cparam.paramid - 1;
	PLpgSQL_var *fc_var;

	/* 获取挂钩数据 */
	fc_params = fc_econtext->ecxt_param_list_info;
	fc_estate = (PLpgSQL_execstate *) fc_params->paramFetchArg;
	Assert(fc_dno >= 0 && fc_dno < fc_estate->ndatums);

	/* 现在我们可以访问目标数据项 */
	fc_var = (PLpgSQL_var *) fc_estate->datums[fc_dno];
	Assert(fc_var->dtype == PLPGSQL_DTYPE_VAR);

	/*
	 * exec_eval_datum() 的内联版本……同时，强制扩展数据项为只读。
	 */
	*fc_op->resvalue = MakeExpandedObjectReadOnly(fc_var->value,
											   fc_var->isnull,
											   -1);
	*fc_op->resnull = fc_var->isnull;

	/* 安全检查 -- 断言应该足够 */
	Assert(fc_var->datatype->typoid == fc_op->d.cparam.paramtype);
}

/*
 * plpgsql_param_eval_recfield		EEOP_PARAM_CALLBACK 步骤的评估
 *
 * 这是针对 DTYPE_RECFIELD 变量的特殊情况，我们从不需要调用 MakeExpandedObjectReadOnly。
 */
static void fc_plpgsql_param_eval_recfield(ExprState *fc_state, ExprEvalStep *fc_op,
							ExprContext *fc_econtext)
{
	ParamListInfo fc_params;
	PLpgSQL_execstate *fc_estate;
	int			fc_dno = fc_op->d.cparam.paramid - 1;
	PLpgSQL_recfield *fc_recfield;
	PLpgSQL_rec *fc_rec;
	ExpandedRecordHeader *fc_erh;

	/* 获取挂钩数据 */
	fc_params = fc_econtext->ecxt_param_list_info;
	fc_estate = (PLpgSQL_execstate *) fc_params->paramFetchArg;
	Assert(fc_dno >= 0 && fc_dno < fc_estate->ndatums);

	/* 现在我们可以访问目标数据项 */
	fc_recfield = (PLpgSQL_recfield *) fc_estate->datums[fc_dno];
	Assert(fc_recfield->dtype == PLPGSQL_DTYPE_RECFIELD);

	/* 内联 exec_eval_datum 的相关部分 */
	fc_rec = (PLpgSQL_rec *) (fc_estate->datums[fc_recfield->recparentno]);
	fc_erh = fc_rec->erh;

	/*
	 * 如果记录变量为 NULL，如果它具有命名的复合类型，则实例化它，否则抱怨。
	 * （这不会改变记录的逻辑状态：它仍然是 NULL。）
	 */
	if (fc_erh == NULL)
	{
		fc_instantiate_empty_record_variable(fc_estate, fc_rec);
		fc_erh = fc_rec->erh;
	}

	/*
	 * 如果我们还没有查找字段的属性，或者自上次以来元组描述符ID发生了变化，则查找字段的属性。
	 */
	if (unlikely(fc_recfield->rectupledescid != fc_erh->er_tupdesc_id))
	{
		if (!expanded_record_lookup_field(fc_erh,
										  fc_recfield->fieldname,
										  &fc_recfield->finfo))
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_COLUMN),
					 errmsg("record \"%s\" has no field \"%s\"",
							fc_rec->refname, fc_recfield->fieldname)));
		fc_recfield->rectupledescid = fc_erh->er_tupdesc_id;
	}

	/* 可以提取字段值。 */
	*fc_op->resvalue = expanded_record_get_field(fc_erh,
											  fc_recfield->finfo.fnumber,
											  fc_op->resnull);

	/* 安全检查——例如，对记录字段是必需的 */
	if (unlikely(fc_recfield->finfo.ftypeid != fc_op->d.cparam.paramtype))
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)",
						fc_op->d.cparam.paramid,
						format_type_be(fc_recfield->finfo.ftypeid),
						format_type_be(fc_op->d.cparam.paramtype))));
}

/*
 * plpgsql_param_eval_generic		EEOP_PARAM_CALLBACK步骤的评估
 *
 * 这处理所有变量类型，但假设我们不需要调用
 * MakeExpandedObjectReadOnly。
 */
static void fc_plpgsql_param_eval_generic(ExprState *fc_state, ExprEvalStep *fc_op,
						   ExprContext *fc_econtext)
{
	ParamListInfo fc_params;
	PLpgSQL_execstate *fc_estate;
	int			fc_dno = fc_op->d.cparam.paramid - 1;
	PLpgSQL_datum *fc_datum;
	Oid			fc_datumtype;
	int32		fc_datumtypmod;

	/* 获取挂钩数据 */
	fc_params = fc_econtext->ecxt_param_list_info;
	fc_estate = (PLpgSQL_execstate *) fc_params->paramFetchArg;
	Assert(fc_dno >= 0 && fc_dno < fc_estate->ndatums);

	/* 现在我们可以访问目标数据项 */
	fc_datum = fc_estate->datums[fc_dno];

	/* 获取数据项的值 */
	fc_exec_eval_datum(fc_estate, fc_datum,
					&fc_datumtype, &fc_datumtypmod,
					fc_op->resvalue, fc_op->resnull);

	/* 安全检查——例如，对记录字段是必需的 */
	if (unlikely(fc_datumtype != fc_op->d.cparam.paramtype))
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)",
						fc_op->d.cparam.paramid,
						format_type_be(fc_datumtype),
						format_type_be(fc_op->d.cparam.paramtype))));
}

/*
 * plpgsql_param_eval_generic_ro		EEOP_PARAM_CALLBACK步骤的评估
 *
 * 这处理所有变量类型，但假设我们需要调用
 * MakeExpandedObjectReadOnly（因此，变量必须是varlena类型）。
 */
static void fc_plpgsql_param_eval_generic_ro(ExprState *fc_state, ExprEvalStep *fc_op,
							  ExprContext *fc_econtext)
{
	ParamListInfo fc_params;
	PLpgSQL_execstate *fc_estate;
	int			fc_dno = fc_op->d.cparam.paramid - 1;
	PLpgSQL_datum *fc_datum;
	Oid			fc_datumtype;
	int32		fc_datumtypmod;

	/* 获取挂钩数据 */
	fc_params = fc_econtext->ecxt_param_list_info;
	fc_estate = (PLpgSQL_execstate *) fc_params->paramFetchArg;
	Assert(fc_dno >= 0 && fc_dno < fc_estate->ndatums);

	/* 现在我们可以访问目标数据项 */
	fc_datum = fc_estate->datums[fc_dno];

	/* 获取数据项的值 */
	fc_exec_eval_datum(fc_estate, fc_datum,
					&fc_datumtype, &fc_datumtypmod,
					fc_op->resvalue, fc_op->resnull);

	/* 安全检查——例如，对记录字段是必需的 */
	if (unlikely(fc_datumtype != fc_op->d.cparam.paramtype))
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)",
						fc_op->d.cparam.paramid,
						format_type_be(fc_datumtype),
						format_type_be(fc_op->d.cparam.paramtype))));

	/* 强制将值设为只读 */
	*fc_op->resvalue = MakeExpandedObjectReadOnly(*fc_op->resvalue,
											   *fc_op->resnull,
											   -1);
}


/*
 * exec_move_row			将一个元组的值移动到记录或行中
 *
 * tup和tupdesc都可能为NULL，如果我们只是在将不确定的
 * 复合NULL分配给目标。或者，可以让tup为NULL而tupdesc不为NULL，在这种情况下，我们将一个NULL的行分配给目标。
 *
 * 由于这使用mcontext作为工作空间，调用者最终应该调用
 * exec_eval_cleanup以防止长期内存泄漏。
 */
static void fc_exec_move_row(PLpgSQL_execstate *fc_estate,
			  PLpgSQL_variable *fc_target,
			  HeapTuple fc_tup, TupleDesc fc_tupdesc)
{
	ExpandedRecordHeader *fc_newerh = NULL;

	/*
	 * 如果目标是RECORD，我们可能能够避免逐字段处理。
	 */
	if (fc_target->dtype == PLPGSQL_DTYPE_REC)
	{
		PLpgSQL_rec *fc_rec = (PLpgSQL_rec *) fc_target;

		/*
		 * 如果我们没有源tupdesc，则只需将记录变量设置为NULL。
		 * （如果我们有源tupdesc但没有元组，则我们将把
		 * 变量设置为一行NULL，而不是。也许这奇怪，但
		 * 向后兼容。）
		 */
		if (fc_tupdesc == NULL)
		{
			if (fc_rec->datatype &&
				fc_rec->datatype->typtype == TYPTYPE_DOMAIN)
			{
				/*
				 * 如果它是复合域，NULL可能不是合法值，
				 * 所以我们需要构建一个空的扩展记录
				 * 并确保进行域类型检查。如果已经有一个扩展记录，则乘坐其查找。
				 */
				fc_newerh = fc_make_expanded_record_for_rec(fc_estate, fc_rec,
													  NULL, fc_rec->erh);
				expanded_record_set_tuple(fc_newerh, NULL, false, false);
				fc_assign_record_var(fc_estate, fc_rec, fc_newerh);
			}
			else
			{
				/* 只需将其清除为NULL */
				if (fc_rec->erh)
					DeleteExpandedObject(ExpandedRecordGetDatum(fc_rec->erh));
				fc_rec->erh = NULL;
			}
			return;
		}

		/*
		 * 构建一个具有适当tupdesc的新扩展记录。
		 */
		fc_newerh = fc_make_expanded_record_for_rec(fc_estate, fc_rec, fc_tupdesc, NULL);

		/*
		 * 如果行类型匹配，或者如果我们反正没有元组，
		 * 我们可以在不逐字段处理的情况下完成赋值。
		 *
		 * 这里的测试顺序或多或少是按照便宜程度排序的。我们
		 * 可以很容易地检测到如果目标被声明为RECORD或者
		 * 与源具有相同的typeid时，它将起作用。但是在从查询
		 * 结果赋值时，源tupdesc通常标记为RECORD
		 * 但实际上与命名复合类型目标物理兼容，因此值得花费额外的周期来检查这一点。
		 */
		if (fc_rec->rectypeid == RECORDOID ||
			fc_rec->rectypeid == fc_tupdesc->tdtypeid ||
			!HeapTupleIsValid(fc_tup) ||
			fc_compatible_tupdescs(fc_tupdesc, expanded_record_get_tupdesc(fc_newerh)))
		{
			if (!HeapTupleIsValid(fc_tup))
			{
				/* 没有数据，因此强制记录进入全NULL状态 */
				deconstruct_expanded_record(fc_newerh);
			}
			else
			{
				/* 不需要强制转换，因此只需分配行值 */
				expanded_record_set_tuple(fc_newerh, fc_tup, true, !fc_estate->atomic);
			}

			/* 完成赋值 */
			fc_assign_record_var(fc_estate, fc_rec, fc_newerh);

			return;
		}
	}

	/*
	 * 否则，解构元组并进行逐字段赋值，
	 * 使用 exec_move_row_from_fields。
	 */
	if (fc_tupdesc && HeapTupleIsValid(fc_tup))
	{
		int			fc_td_natts = fc_tupdesc->natts;
		Datum	   *fc_values;
		bool	   *fc_nulls;
		Datum		fc_values_local[64];
		bool		fc_nulls_local[64];

		/*
		 * 需要工作区数组。如果 td_natts 足够小，则使用本地
		 * 数组以避免进行 palloc。即使它不小，我们也可以
		 * 在一个 palloc 块中分配 Datum 和 isnull 数组。
		 */
		if (fc_td_natts <= lengthof(fc_values_local))
		{
			fc_values = fc_values_local;
			fc_nulls = fc_nulls_local;
		}
		else
		{
			char	   *fc_chunk;

			fc_chunk = eval_mcontext_alloc(fc_estate,
										fc_td_natts * (sizeof(Datum) + sizeof(bool)));
			fc_values = (Datum *) fc_chunk;
			fc_nulls = (bool *) (fc_chunk + fc_td_natts * sizeof(Datum));
		}

		heap_deform_tuple(fc_tup, fc_tupdesc, fc_values, fc_nulls);

		fc_exec_move_row_from_fields(fc_estate, fc_target, fc_newerh,
								  fc_values, fc_nulls, fc_tupdesc);
	}
	else
	{
		/*
		 * 赋值全为 null。
		 */
		fc_exec_move_row_from_fields(fc_estate, fc_target, fc_newerh,
								  NULL, NULL, NULL);
	}
}

/*
 * 验证 PLpgSQL_rec 的 rectypeid 是否是最新的。
 */
static void fc_revalidate_rectypeid(PLpgSQL_rec *fc_rec)
{
	PLpgSQL_type *fc_typ = fc_rec->datatype;
	TypeCacheEntry *fc_typentry;

	if (fc_rec->rectypeid == RECORDOID)
		return;					/* 它是 RECORD，因此无需处理 */
	Assert(fc_typ != NULL);
	if (fc_typ->tcache &&
		fc_typ->tcache->tupDesc_identifier == fc_typ->tupdesc_id)
	{
		/*
		 * 尽管 *typ 已知是最新的，但 rectypeid 可能不是，
		 * 因为 *rec 在每个函数启动期间从一个我们没有
		 * 合适方法更新的副本中克隆。因此，在返回之前强制修复
		 * rectypeid。
		 */
		fc_rec->rectypeid = fc_typ->typoid;
		return;
	}

	/*
	 * typcache 条目已失效，因此尽可能重新查找类型名称，
	 * 然后重新检查类型 OID。如果我们没有 TypeName，
	 * 那么我们只能继续使用已获得的 OID。
	 */
	if (fc_typ->origtypname != NULL)
	{
		/* 这一部分应与 pl_gram.y 中的 parse_datatype() 匹配 */
		typenameTypeIdAndMod(NULL, fc_typ->origtypname,
							 &fc_typ->typoid,
							 &fc_typ->atttypmod);
	}

	/* 这一部分应与 pl_comp.c 中的 build_datatype() 匹配 */
	fc_typentry = lookup_type_cache(fc_typ->typoid,
								 TYPECACHE_TUPDESC |
								 TYPECACHE_DOMAIN_BASE_INFO);
	if (fc_typentry->typtype == TYPTYPE_DOMAIN)
		fc_typentry = lookup_type_cache(fc_typentry->domainBaseType,
									 TYPECACHE_TUPDESC);
	if (fc_typentry->tupDesc == NULL)
	{
		/*
		 * 如果我们到这里，用户试图用非复合类型替换复合类型。
		 * 我们不会支持这种操作。
		 */
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("type %s is not composite",
						format_type_be(fc_typ->typoid))));
	}

	/*
	 * 更新 tcache 和 tupdesc_id。由于我们不支持更改为
	 * 非复合类型，*typ 的其余部分都不需要更改。
	 */
	fc_typ->tcache = fc_typentry;
	fc_typ->tupdesc_id = fc_typentry->tupDesc_identifier;

	/*
	 * 也更新 *rec。（我们会根据需要处理辅助 RECFIELD。）
	 */
	fc_rec->rectypeid = fc_typ->typoid;
}

/*
 * 构建适合赋值给 "rec" 的扩展记录对象。
 *
 * 调用者必须提供源元组描述符或源扩展
 * 记录（不能两者都提供）。如果记录变量已声明类型为
 * RECORD，它将采用源的行类型。即使不是，我们也可能能够
 * 利用源扩展记录以节省 typcache 查找。
 *
 * 调用者必须用数据填充对象，然后调用 assign_record_var()。
 *
 * 新的记录最初放入 mcontext 中，因此如果在到达 assign_record_var() 之前失败，将会被清理。
 */
static ExpandedRecordHeader *
fc_make_expanded_record_for_rec(PLpgSQL_execstate *fc_estate,
							 PLpgSQL_rec *fc_rec,
							 TupleDesc fc_srctupdesc,
							 ExpandedRecordHeader *fc_srcerh)
{
	ExpandedRecordHeader *fc_newerh;
	MemoryContext fc_mcontext = get_eval_mcontext(fc_estate);

	if (fc_rec->rectypeid != RECORDOID)
	{
		/*
		 * 在使用之前，确保 rec->rectypeid 是最新的。
		 */
		fc_revalidate_rectypeid(fc_rec);

		/*
		 * 新记录必须是所需类型，但也许 srcerh 已经
		 * 完成了所有相同的查找。
		 */
		if (fc_srcerh && fc_rec->rectypeid == fc_srcerh->er_decltypeid)
			fc_newerh = make_expanded_record_from_exprecord(fc_srcerh,
														 fc_mcontext);
		else
			fc_newerh = make_expanded_record_from_typeid(fc_rec->rectypeid, -1,
													  fc_mcontext);
	}
	else
	{
		/*
		 * 我们将采用输入 tupdesc。如果 srcerh 不是复合
		 * 域，我们仍然可以使用 make_expanded_record_from_exprecord。
		 * （如果是，我们实际上采用了它的基础类型。）
		 */
		if (fc_srcerh && !ExpandedRecordIsDomain(fc_srcerh))
			fc_newerh = make_expanded_record_from_exprecord(fc_srcerh,
														 fc_mcontext);
		else
		{
			if (!fc_srctupdesc)
				fc_srctupdesc = expanded_record_get_tupdesc(fc_srcerh);
			fc_newerh = make_expanded_record_from_tupdesc(fc_srctupdesc,
													   fc_mcontext);
		}
	}

	return fc_newerh;
}

/*
 * exec_move_row_from_fields 将字段值数组移入记录或行
 *
 * 在给记录赋值时，调用者必须已经创建了一个合适的
 * 新扩展记录对象 newerh。当赋值给行时，传入 NULL。
 *
 * tupdesc 描述了输入行，可能具有不同的列
 * 类型和/或与目标不同的丢弃列位置。
 * values/nulls/tupdesc 如果我们只想将 NULL 赋值给
 * 记录或行的所有字段，则可以全部为 NULL。
 *
 * 由于这使用 mcontext 作为工作空间，调用者最终应该调用
 * exec_eval_cleanup 以防止长期内存泄漏。
 */
static void fc_exec_move_row_from_fields(PLpgSQL_execstate *fc_estate,
						  PLpgSQL_variable *fc_target,
						  ExpandedRecordHeader *fc_newerh,
						  Datum *fc_values, bool *fc_nulls,
						  TupleDesc fc_tupdesc)
{
	int			fc_td_natts = fc_tupdesc ? fc_tupdesc->natts : 0;
	int			fc_fnum;
	int			fc_anum;
	int			fc_strict_multiassignment_level = 0;

	/*
	 * 额外的检查 strict strict_multi_assignment 仅在指定
	 * 输入 tupdesc 时才可以激活。
	 */
	if (fc_tupdesc != NULL)
	{
		if (plpgsql_extra_errors & PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT)
			fc_strict_multiassignment_level = ERROR;
		else if (plpgsql_extra_warnings & PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT)
			fc_strict_multiassignment_level = WARNING;
	}

	/* 处理 RECORD 目标情况 */
	if (fc_target->dtype == PLPGSQL_DTYPE_REC)
	{
		PLpgSQL_rec *fc_rec = (PLpgSQL_rec *) fc_target;
		TupleDesc	fc_var_tupdesc;
		Datum		fc_newvalues_local[64];
		bool		fc_newnulls_local[64];

		Assert(fc_newerh != NULL); /* 调用者必须已经构建了新对象 */

		fc_var_tupdesc = expanded_record_get_tupdesc(fc_newerh);

		/*
		 * 如果需要，强制转换字段值。这可能涉及
		 * 处理不同的丢弃列集和/或强制转换单个列
		 * 类型。这有点麻烦，但历史上 plpgsql 已允许
		 * 这样做，因此我们保留这种行为。然而，值得快速检查
		 * tupdesc 是否相同。（由于 expandedrecord.c
		 * 更倾向于使用来自 typcache 的引用计数 tupdescs，
		 * 行类型相同的扩展记录将具有指针相等的 tupdescs。）
		 */
		if (fc_var_tupdesc != fc_tupdesc)
		{
			int			fc_vtd_natts = fc_var_tupdesc->natts;
			Datum	   *fc_newvalues;
			bool	   *fc_newnulls;

			/*
			 * 需要工作空间数组。如果 vtd_natts 足够小，使用
			 * 本地数组以避免进行 palloc。即使它不小，我们也可以
			 * 在一个 palloc 块中同时分配 Datum 和 isnull 数组。
			 */
			if (fc_vtd_natts <= lengthof(fc_newvalues_local))
			{
				fc_newvalues = fc_newvalues_local;
				fc_newnulls = fc_newnulls_local;
			}
			else
			{
				char	   *fc_chunk;

				fc_chunk = eval_mcontext_alloc(fc_estate,
											fc_vtd_natts * (sizeof(Datum) + sizeof(bool)));
				fc_newvalues = (Datum *) fc_chunk;
				fc_newnulls = (bool *) (fc_chunk + fc_vtd_natts * sizeof(Datum));
			}

			/* 遍历目标列 */
			fc_anum = 0;
			for (fc_fnum = 0; fc_fnum < fc_vtd_natts; fc_fnum++)
			{
				Form_pg_attribute fc_attr = TupleDescAttr(fc_var_tupdesc, fc_fnum);
				Datum		fc_value;
				bool		fc_isnull;
				Oid			fc_valtype;
				int32		fc_valtypmod;

				if (fc_attr->attisdropped)
				{
					/* expanded_record_set_fields 应该忽略此列 */
					continue;	/* 跳过记录中的丢弃列 */
				}

				while (fc_anum < fc_td_natts &&
					   TupleDescAttr(fc_tupdesc, fc_anum)->attisdropped)
					fc_anum++;		/* 跳过元组中的丢弃列 */

				if (fc_anum < fc_td_natts)
				{
					fc_value = fc_values[fc_anum];
					fc_isnull = fc_nulls[fc_anum];
					fc_valtype = TupleDescAttr(fc_tupdesc, fc_anum)->atttypid;
					fc_valtypmod = TupleDescAttr(fc_tupdesc, fc_anum)->atttypmod;
					fc_anum++;
				}
				else
				{
					/* 目标列没有来源 */
					fc_value = (Datum) 0;
					fc_isnull = true;
					fc_valtype = UNKNOWNOID;
					fc_valtypmod = -1;

					/* 当源值缺失时 */
					if (fc_strict_multiassignment_level)
						ereport(fc_strict_multiassignment_level,
								(errcode(ERRCODE_DATATYPE_MISMATCH),
								 errmsg("number of source and target fields in assignment does not match"),
						/* 翻译者: %s 代表额外检查的名称 */
								 errdetail("%s check of %s is active.",
										   "strict_multi_assignment",
										   fc_strict_multiassignment_level == ERROR ? "extra_errors" :
										   "extra_warnings"),
								 errhint("Make sure the query returns the exact list of columns.")));
				}

				/* 如有必要，将新值转换为正确的类型。 */
				fc_newvalues[fc_fnum] = fc_exec_cast_value(fc_estate,
												  fc_value,
												  &fc_isnull,
												  fc_valtype,
												  fc_valtypmod,
												  fc_attr->atttypid,
												  fc_attr->atttypmod);
				fc_newnulls[fc_fnum] = fc_isnull;
			}

			/*
			 * 当 strict_multiassignment 额外检查处于活动状态时，确保
			 * 没有未赋值的源属性。
			 */
			if (fc_strict_multiassignment_level && fc_anum < fc_td_natts)
			{
				/* 跳过源描述符中的丢弃列 */
				while (fc_anum < fc_td_natts &&
					   TupleDescAttr(fc_tupdesc, fc_anum)->attisdropped)
					fc_anum++;

				if (fc_anum < fc_td_natts)
					ereport(fc_strict_multiassignment_level,
							(errcode(ERRCODE_DATATYPE_MISMATCH),
							 errmsg("number of source and target fields in assignment does not match"),
					/* 翻译者: %s 代表额外检查的名称 */
							 errdetail("%s check of %s is active.",
									   "strict_multi_assignment",
									   fc_strict_multiassignment_level == ERROR ? "extra_errors" :
									   "extra_warnings"),
							 errhint("Make sure the query returns the exact list of columns.")));
			}

			fc_values = fc_newvalues;
			fc_nulls = fc_newnulls;
		}

		/* 将强制转换后的字段值插入新的扩展记录中 */
		expanded_record_set_fields(fc_newerh, fc_values, fc_nulls, !fc_estate->atomic);

		/* 完成赋值 */
		fc_assign_record_var(fc_estate, fc_rec, fc_newerh);

		return;
	}

	/* newerh 在非 RECORD 情况下不应被传入 */
	Assert(fc_newerh == NULL);

	/*
	 * 对于一行，我们将单个字段值赋值给行指向的变量。
	 *
	 * 注意：此代码和上面的记录代码会默默忽略来源中的额外
	 * 列，并假设缺失列为 NULL。这相当可疑，但这是
	 * 历史行为。
	 *
	 * 如果我们完全没有输入数据，我们将为
	 * 行变量的所有列赋值为 NULL。
	 */
	if (fc_target->dtype == PLPGSQL_DTYPE_ROW)
	{
		PLpgSQL_row *fc_row = (PLpgSQL_row *) fc_target;

		fc_anum = 0;
		for (fc_fnum = 0; fc_fnum < fc_row->nfields; fc_fnum++)
		{
			PLpgSQL_var *fc_var;
			Datum		fc_value;
			bool		fc_isnull;
			Oid			fc_valtype;
			int32		fc_valtypmod;

			fc_var = (PLpgSQL_var *) (fc_estate->datums[fc_row->varnos[fc_fnum]]);

			while (fc_anum < fc_td_natts &&
				   TupleDescAttr(fc_tupdesc, fc_anum)->attisdropped)
				fc_anum++;			/* 跳过元组中的丢弃列 */

			if (fc_anum < fc_td_natts)
			{
				fc_value = fc_values[fc_anum];
				fc_isnull = fc_nulls[fc_anum];
				fc_valtype = TupleDescAttr(fc_tupdesc, fc_anum)->atttypid;
				fc_valtypmod = TupleDescAttr(fc_tupdesc, fc_anum)->atttypmod;
				fc_anum++;
			}
			else
			{
				/* 目标列没有来源 */
				fc_value = (Datum) 0;
				fc_isnull = true;
				fc_valtype = UNKNOWNOID;
				fc_valtypmod = -1;

				if (fc_strict_multiassignment_level)
					ereport(fc_strict_multiassignment_level,
							(errcode(ERRCODE_DATATYPE_MISMATCH),
							 errmsg("number of source and target fields in assignment does not match"),
					/* 翻译者: %s 代表额外检查的名称 */
							 errdetail("%s check of %s is active.",
									   "strict_multi_assignment",
									   fc_strict_multiassignment_level == ERROR ? "extra_errors" :
									   "extra_warnings"),
							 errhint("Make sure the query returns the exact list of columns.")));
			}

			fc_exec_assign_value(fc_estate, (PLpgSQL_datum *) fc_var,
							  fc_value, fc_isnull, fc_valtype, fc_valtypmod);
		}

		/*
		 * 当 strict_multiassignment 附加检查激活时，确保没有
		 * 未分配的源属性。
		 */
/* 跳过元组中的丢弃列 */
/* 翻译者：%s 代表附加检查的名称 */
/*
 * compatible_tupdescs: 检测两个 tupdescs 是否物理兼容
 *
 * TRUE 表示满足 src_tupdesc 的元组可以直接用作
 * 使用 dst_tupdesc 的复合变量的值。
 */
/* 我们是否可以允许 src_tupdesc 具有附加列？ */
/* 正常列必须按类型和 typmod 匹配 */
/* 只要长度/对齐匹配，丢弃的列是可以的 */
/* ----------
 * make_tuple_from_row		从行对象的值创建一个元组
 *
 * NULL 返回指示行类型不匹配；调用者必须引发适当的错误
 *
 * 结果元组是在调用者的上下文中新分配的。某些垃圾
 * 可能仍留在 eval_mcontext 中。
 * ----------
 */
/* 将列留为 null */
/* XXX 我们是否也应该坚持 typmod 匹配？ */
/*
 * deconstruct_composite_datum		从复合 Datum 中提取元组+tupdesc
 *
 * 调用者必须提供一个 HeapTupleData 变量，在其中我们设置一个
 * 指向复合数据体的元组头。为了使元组
 * 值的生命周期超出该变量，调用者需要应用 heap_copytuple...
 * 不过当前调用者只需要短暂的元组值。
 *
 * 返回指向数据行类型的 TupleDesc 的指针。
 * 调用者负责在使用完后调用 ReleaseTupleDesc。
 *
 * 注意：确保值是复合类型的责任在于调用者。
 * 此外，最好在短暂的上下文中调用此函数，因为它可能会导致内存泄漏。
 */
/* 获取元组体（请注意，这可能涉及解压） */
/* 不能到这里，但让编译器保持安静 */
/* 转交错误给 Perl */
/*
 * exec_move_row_from_datum		将复合 Datum 移入记录或行
 *
 * 这相当于 deconstruct_composite_datum() 然后执行 exec_move_row()，
 * 但如果 Datum 是一个展开记录引用，我们可以优化处理。
 *
 * 注意：确保值是复合类型的责任在于调用者。
 */
/* 检查源是否为展开记录 */
/* 这些情况适用于目标为记录而非行的情况... */
/*
			 * 如果它是已经存储在变量中的相同记录，则无事可做。
			 * 这只会发生在像 "r := r" 的愚蠢情况中，
			 * 但我们需要某种检查以避免可能释放变量的
			 * 活值。请注意，即使我们拥有的是 R/O 指针，这也适用。
			 */
/*
			 * 确保在使用之前 rec->rectypeid 是最新的。
			 */
		if (fc_strict_multiassignment_level && fc_anum < fc_td_natts)
		{
			while (fc_anum < fc_td_natts &&
				   TupleDescAttr(fc_tupdesc, fc_anum)->attisdropped)
				fc_anum++;			/* 跳过元组中的丢弃列 */

			if (fc_anum < fc_td_natts)
				ereport(fc_strict_multiassignment_level,
						(errcode(ERRCODE_DATATYPE_MISMATCH),
						 errmsg("number of source and target fields in assignment does not match"),
				/* 翻译者: %s 代表额外检查的名称 */
						 errdetail("%s check of %s is active.",
								   "strict_multi_assignment",
								   fc_strict_multiassignment_level == ERROR ? "extra_errors" :
								   "extra_warnings"),
						 errhint("Make sure the query returns the exact list of columns.")));
		}

		return;
	}

	elog(ERROR, "unsupported target type: %d", fc_target->dtype);
}

/*
			 * 否则我们需要一个新的扩展记录对象。在这里创建
			 * 它，希望能够依赖源对象先前的typcache查找。
			 */
static bool fc_compatible_tupdescs(TupleDesc fc_src_tupdesc, TupleDesc fc_dst_tupdesc)
{
	int			fc_i;

	/*
			 * 如果扩展记录包含有效的元组表示，并且我们不
			 * 需要行类型转换，那么复制元组可能比逐字段
			 * 处理更快。（这与之前的检查没有重复，因为
			 * 在这里我们会捕捉到记录变量之前为空的情况。）
			 */
	if (fc_dst_tupdesc->natts != fc_src_tupdesc->natts)
		return false;

	for (fc_i = 0; fc_i < fc_dst_tupdesc->natts; fc_i++)
	{
		Form_pg_attribute fc_dattr = TupleDescAttr(fc_dst_tupdesc, fc_i);
		Form_pg_attribute fc_sattr = TupleDescAttr(fc_src_tupdesc, fc_i);

		if (fc_dattr->attisdropped != fc_sattr->attisdropped)
			return false;
		if (!fc_dattr->attisdropped)
		{
			/*
			 * 需要对空源记录进行特殊处理，否则下面的代码将
			 * 泄漏newerh。
			 */
			if (fc_dattr->atttypid != fc_sattr->atttypid ||
				(fc_dattr->atttypmod >= 0 &&
				 fc_dattr->atttypmod != fc_sattr->atttypmod))
				return false;
		}
		else
		{
			/* 将newerh设置为NULL行 */
			if (fc_dattr->attlen != fc_sattr->attlen ||
				fc_dattr->attalign != fc_sattr->attalign)
				return false;
		}
	}
	return true;
}

/* 记录目标仅限于情况的结束 */
static HeapTuple fc_make_tuple_from_row(PLpgSQL_execstate *fc_estate,
					PLpgSQL_row *fc_row,
					TupleDesc fc_tupdesc)
{
	int			fc_natts = fc_tupdesc->natts;
	HeapTuple	fc_tuple;
	Datum	   *fc_dvalues;
	bool	   *fc_nulls;
	int			fc_i;

	if (fc_natts != fc_row->nfields)
		return NULL;

	fc_dvalues = (Datum *) eval_mcontext_alloc0(fc_estate, fc_natts * sizeof(Datum));
	fc_nulls = (bool *) eval_mcontext_alloc(fc_estate, fc_natts * sizeof(bool));

	for (fc_i = 0; fc_i < fc_natts; fc_i++)
	{
		Oid			fc_fieldtypeid;
		int32		fc_fieldtypmod;

		if (TupleDescAttr(fc_tupdesc, fc_i)->attisdropped)
		{
			fc_nulls[fc_i] = true;	/*
		 * 如果源扩展记录为空，我们应该将其视为
		 * NULL元组值。（我们不太可能看到这样的情况，
		 * 但我们必须检查这一点；deconstruct_expanded_record
		 * 会导致逻辑状态的变化，这是不允许的。）
		 */
			continue;
		}

		fc_exec_eval_datum(fc_estate, fc_estate->datums[fc_row->varnos[fc_i]],
						&fc_fieldtypeid, &fc_fieldtypmod,
						&fc_dvalues[fc_i], &fc_nulls[fc_i]);
		if (fc_fieldtypeid != TupleDescAttr(fc_tupdesc, fc_i)->atttypid)
			return NULL;
		/*
		 * 否则，确保源记录被解构，并从其字段值进行分配。
		 */
	}

	fc_tuple = heap_form_tuple(fc_tupdesc, fc_dvalues, fc_nulls);

	return fc_tuple;
}

/*
		 * 不，我们得到了一个普通的复合Datum。解构它；
		 * 但我们不使用deconstruct_composite_datum()
		 * ，因为我们可能可以跳过调用lookup_rowtype_tupdesc()。
		 */
static TupleDesc fc_deconstruct_composite_datum(Datum fc_value, HeapTupleData *fc_tmptup)
{
	HeapTupleHeader fc_td;
	Oid			fc_tupType;
	int32		fc_tupTypmod;

	/* 确保任何去烘焙的数据最终落入eval_mcontext */
	fc_td = DatumGetHeapTupleHeader(fc_value);

	/* 构建一个临时 HeapTuple 控制结构 */
	fc_tmptup->t_len = HeapTupleHeaderGetDatumLength(fc_td);
	ItemPointerSetInvalid(&(fc_tmptup->t_self));
	fc_tmptup->t_tableOid = InvalidOid;
	fc_tmptup->t_data = fc_td;

	/* 提取行类型信息并找到 tupdesc */
	fc_tupType = HeapTupleHeaderGetTypeId(fc_td);
	fc_tupTypmod = HeapTupleHeaderGetTypMod(fc_td);
	return lookup_rowtype_tupdesc(fc_tupType, fc_tupTypmod);
}

/* 提取行类型信息 */
static void fc_exec_move_row_from_datum(PLpgSQL_execstate *fc_estate,
						 PLpgSQL_variable *fc_target,
						 Datum fc_value)
{
	/* 现在，如果目标是记录而不是行，可能可以进行优化... */
	if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(fc_value)))
	{
		ExpandedRecordHeader *fc_erh = (ExpandedRecordHeader *) DatumGetEOHP(fc_value);
		ExpandedRecordHeader *fc_newerh = NULL;

		Assert(fc_erh->er_magic == ER_MAGIC);

		/*
 * 如果我们在目标变量中已经有一个扩展记录对象，并且源数据具有匹配的行类型，那么我们可以跳过创建新的扩展记录，只需将元组与 expanded_record_set_tuple 进行赋值。我们认为只有在相同名称的复合类型或相同注册的行类型时才存在行类型匹配。（在这里检查以拒绝匿名行类型应该是多余的，但我们还是要安全起见。）
 */
		if (fc_target->dtype == PLPGSQL_DTYPE_REC)
		{
			PLpgSQL_rec *fc_rec = (PLpgSQL_rec *) fc_target;

			/*
 * 如果源数据具有与目标变量兼容的行类型，只需构建一个新的扩展记录并将元组赋值给它。在这里使用 make_expanded_record_from_typeid() 可以比下面的代码节省一个类型缓存查找。
 */
			if (fc_erh == fc_rec->erh)
				return;

			/*
 * 否则，我们需要转换，因此必须采取较为复杂的方式进行处理。
 */
			fc_revalidate_rectypeid(fc_rec);

			/*
 * ROW 目标，或不可优化的 RECORD 目标，因此我们必须进行查找以获取源数据的 tupdesc。
 */
			if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(fc_value)) &&
				(fc_rec->rectypeid == fc_erh->er_decltypeid ||
				 (fc_rec->rectypeid == RECORDOID &&
				  !ExpandedRecordIsDomain(fc_erh))))
			{
				fc_assign_record_var(fc_estate, fc_rec, fc_erh);
				return;
			}

			/* 执行移动 */
			if (fc_rec->erh &&
				(fc_erh->flags & ER_FLAG_FVALUE_VALID) &&
				fc_erh->er_typeid == fc_rec->erh->er_typeid &&
				(fc_erh->er_typeid != RECORDOID ||
				 (fc_erh->er_typmod == fc_rec->erh->er_typmod &&
				  fc_erh->er_typmod >= 0)))
			{
				expanded_record_set_tuple(fc_rec->erh, fc_erh->fvalue,
										  true, !fc_estate->atomic);
				return;
			}

			/* 释放 tupdesc 使用计数 */
			fc_newerh = fc_make_expanded_record_for_rec(fc_estate, fc_rec, NULL, fc_erh);

			/*
 * 如果我们尚未创建一个扩展记录以保存记录变量的值，则进行创建。扩展记录将是“空”的，因此这不会改变记录变量的逻辑状态：它仍然是 NULL。
 * 然而，现在我们将拥有一个 tupdesc，可以用于例如查找字段。
 */
			if ((fc_erh->flags & ER_FLAG_FVALUE_VALID) &&
				(fc_rec->rectypeid == RECORDOID ||
				 fc_rec->rectypeid == fc_erh->er_typeid))
			{
				expanded_record_set_tuple(fc_newerh, fc_erh->fvalue,
										  true, !fc_estate->atomic);
				fc_assign_record_var(fc_estate, fc_rec, fc_newerh);
				return;
			}

			/* 否则调用者错误 */
			if (ExpandedRecordIsEmpty(fc_erh))
			{
				/* 如果声明的类型是 RECORD，我们无法实例化 */
				deconstruct_expanded_record(fc_newerh);
				fc_assign_record_var(fc_estate, fc_rec, fc_newerh);
				return;
			}
		}						/* 确保在使用之前 rec->rectypeid 是最新的 */

		/* 好的，执行此操作 */
		if (ExpandedRecordIsEmpty(fc_erh))
		{
			fc_exec_move_row(fc_estate, fc_target, NULL,
						  expanded_record_get_tupdesc(fc_erh));
			return;
		}

		/* ----------
 * convert_value_to_string			将非空 Datum 转换为 C 字符串
 *
 * 注意：结果在 estate 的 eval_mcontext 中，并将在下一个 exec_eval_cleanup() 调用时被清除。调用的输出函数可能还会在那儿留下额外的残留物，因此如果不这样做，仅仅 pfree 结果字符串不足以避免内存泄漏。在大多数用法中，传入的 Datum 也在该上下文中（如果是按引用传递），因此无论如何都需要进行 exec_eval_cleanup() 调用。
 *
 * 注意：不缓存转换函数查找会影响性能。然而，目前这个函数并没有在任何地方使用，看起来额外的目录查找不算什么大事。
 * ----------
 */
		deconstruct_expanded_record(fc_erh);
		fc_exec_move_row_from_fields(fc_estate, fc_target, fc_newerh,
								  fc_erh->dvalues, fc_erh->dnulls,
								  expanded_record_get_tupdesc(fc_erh));
	}
	else
	{
		/* ----------
 * exec_cast_value			如果需要，将值转换类型
 *
 * 注意 *isnull 是一个输入和输出参数。虽然不太可能转换操作会从非空产生 null 或反之，但原则上是可能发生的。
 *
 * 注意：estate 的 eval_mcontext 用于临时存储，并且如果我们必须转换为按引用传递的数据类型，它也可能包含结果 Datum。完成结果处理后，请务必调用 exec_eval_cleanup()。
 * ----------
 */
		HeapTupleHeader fc_td;
		HeapTupleData fc_tmptup;
		Oid			fc_tupType;
		int32		fc_tupTypmod;
		TupleDesc	fc_tupdesc;
		MemoryContext fc_oldcontext;

		/*
	 * 如果给定值的类型不是请求的类型，则进行转换。
	 */
		fc_oldcontext = MemoryContextSwitchTo(get_eval_mcontext(fc_estate));
		/* 确保任何去烘焙的数据最终落入eval_mcontext */
		fc_td = DatumGetHeapTupleHeader(fc_value);
		MemoryContextSwitchTo(fc_oldcontext);

		/* 构建一个临时 HeapTuple 控制结构 */
		fc_tmptup.t_len = HeapTupleHeaderGetDatumLength(fc_td);
		ItemPointerSetInvalid(&(fc_tmptup.t_self));
		fc_tmptup.t_tableOid = InvalidOid;
		fc_tmptup.t_data = fc_td;

		/* ----------
 * get_cast_hashentry			查找如何执行类型转换
 *
 * 如果表达式需要被评估，则返回一个 plpgsql_CastHashEntry，
 * 如果转换只是一个无操作的重新标记，则返回 NULL。 如果需要执行的工作，
 * cast_exprstate 字段包含基于 CaseTestExpr 输入的表达式评估树，
 * 在执行时应将 cast_in_use 字段设置为 true。
 * ----------
 */
		fc_tupType = HeapTupleHeaderGetTypeId(fc_td);
		fc_tupTypmod = HeapTupleHeaderGetTypMod(fc_td);

		/* 查找现有条目 */
		if (fc_target->dtype == PLPGSQL_DTYPE_REC)
		{
			PLpgSQL_rec *fc_rec = (PLpgSQL_rec *) fc_target;

			/* 如果是新条目则初始化 */
			if (fc_rec->erh &&
				fc_tupType == fc_rec->erh->er_typeid &&
				(fc_tupType != RECORDOID ||
				 (fc_tupTypmod == fc_rec->erh->er_typmod &&
				  fc_tupTypmod >= 0)))
			{
				expanded_record_set_tuple(fc_rec->erh, &fc_tmptup,
										  true, !fc_estate->atomic);
				return;
			}

			/* 我们需要第二次查找以查看 cast_expr_hash 条目是否存在 */
			if (fc_rec->rectypeid == RECORDOID || fc_rec->rectypeid == fc_tupType)
			{
				ExpandedRecordHeader *fc_newerh;
				MemoryContext fc_mcontext = get_eval_mcontext(fc_estate);

				fc_newerh = make_expanded_record_from_typeid(fc_tupType, fc_tupTypmod,
														  fc_mcontext);
				expanded_record_set_tuple(fc_newerh, &fc_tmptup,
										  true, !fc_estate->atomic);
				fc_assign_record_var(fc_estate, fc_rec, fc_newerh);
				return;
			}

			/* 如果是新表达式条目则初始化 */
		}

		/* 始终使用有效链接以避免第二次哈希查找 */
		fc_tupdesc = lookup_rowtype_tupdesc(fc_tupType, fc_tupTypmod);

		/*
		 * 我们之前没有查找过这个强制转换，或者查找过但缓存
		 * 的表达式已失效。
		 */
		fc_exec_move_row(fc_estate, fc_target, &fc_tmptup, fc_tupdesc);

		/*
		 * 如果有旧的缓存表达式，则将其丢弃。
		 */
		ReleaseTupleDesc(fc_tupdesc);
	}
}

/*
		 * 由于我们可能会轻易失败（没有这样的强制转换），
		 * 在短暂的 eval_mcontext 中构造一个临时强制转换表达式树，
		 * 然后如果成功，将其保存为 CachedExpression。
		 */
static void fc_instantiate_empty_record_variable(PLpgSQL_execstate *fc_estate, PLpgSQL_rec *fc_rec)
{
	Assert(fc_rec->erh == NULL);	/*
		 * 我们使用 CaseTestExpr 作为强制转换树的基础，
		 * 因为插入源值是非常便宜的。
		 */

	/*
		 * 应用强制转换。我们使用特殊的强制转换上下文
		 * COERCION_PLPGSQL 以匹配 plpgsql 的历史行为，即
		 * 在赋值级别上不可用的任何转换都将作为 I/O 强制转换实现。
		 * （我们认为在 EXPLICIT 级别存在的转换路径上
		 * 更喜欢 I/O 强制转换是有些可疑的。 改变这一点将导致
		 * 各种小的行为差异，然而，想要显式转换行为的用户
		 * 始终可以编写一个显式转换。）
		 *
		 * 如果源类型是 UNKNOWN，coerce_to_target_type 将失败（它只
		 * 期望在 Const 输入节点中看到它），因此不要调用它；
		 * 我们将改为应用 CoerceViaIO。 同样，它当前不适用于
		 * 将 RECORD 强制转换为某种其他类型，因此也跳过该操作。
		 */
	if (fc_rec->rectypeid == RECORDOID)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("record \"%s\" is not assigned yet", fc_rec->refname),
				 errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));

	/*
		 * 如果根据解析器没有转换路径，回退到使用
		 * I/O 强制转换；这在语义上是有可疑的，但匹配 plpgsql 的
		 * 历史行为。 无论如何，我们需要类似的东西来处理
		 * UNKNOWN 文本字面量。 （这可能现在仅在 srctype 为
		 * UNKNOWN/RECORD 的情况下可达。）
		 */
	fc_revalidate_rectypeid(fc_rec);

	/* 注意：我们不在表达式树上标记排序规则 */
	fc_rec->erh = make_expanded_record_from_typeid(fc_rec->rectypeid, -1,
												fc_estate->datum_context);
}

/* 计划表达式并构建 CachedExpression */
static char * fc_convert_value_to_string(PLpgSQL_execstate *fc_estate, Datum fc_value, Oid fc_valtype)
{
	char	   *fc_result;
	MemoryContext fc_oldcontext;
	Oid			fc_typoutput;
	bool		fc_typIsVarlena;

	fc_oldcontext = MemoryContextSwitchTo(get_eval_mcontext(fc_estate));
	getTypeOutputInfo(fc_valtype, &fc_typoutput, &fc_typIsVarlena);
	fc_result = OidOutputFunctionCall(fc_typoutput, fc_value);
	MemoryContextSwitchTo(fc_oldcontext);

	return fc_result;
}

/* 检测我们是否有无操作（RelabelType）强制转换 */
static inline Datum
fc_exec_cast_value(PLpgSQL_execstate *fc_estate,
				Datum fc_value, bool *fc_isnull,
				Oid fc_valtype, int32 fc_valtypmod,
				Oid fc_reqtype, int32 fc_reqtypmod)
{
	/* 现在我们可以填写表达式哈希表条目。 */
	if (fc_valtype != fc_reqtype ||
		(fc_valtypmod != fc_reqtypmod && fc_reqtypmod != -1))
	{
		/* 确保也重置 exprstate 哈希表条目。 */
		fc_value = fc_do_cast_value(fc_estate, fc_value, fc_isnull, fc_valtype, fc_valtypmod,
							  fc_reqtype, fc_reqtypmod);
	}

	return fc_value;
}

/* 如果我们确定这是一个无操作转换，则完成。 */
static Datum fc_do_cast_value(PLpgSQL_execstate *fc_estate,
			  Datum fc_value, bool *fc_isnull,
			  Oid fc_valtype, int32 fc_valtypmod,
			  Oid fc_reqtype, int32 fc_reqtypmod)
{
	plpgsql_CastHashEntry *fc_cast_entry;

	fc_cast_entry = fc_get_cast_hashentry(fc_estate,
									fc_valtype, fc_valtypmod,
									fc_reqtype, fc_reqtypmod);
	if (fc_cast_entry)
	{
		ExprContext *fc_econtext = fc_estate->eval_econtext;
		MemoryContext fc_oldcontext;

		fc_oldcontext = MemoryContextSwitchTo(get_eval_mcontext(fc_estate));

		fc_econtext->caseValue_datum = fc_value;
		fc_econtext->caseValue_isNull = *fc_isnull;

		fc_cast_entry->cast_in_use = true;

		fc_value = ExecEvalExpr(fc_cast_entry->cast_exprstate, fc_econtext,
							 fc_isnull);

		fc_cast_entry->cast_in_use = false;

		MemoryContextSwitchTo(fc_oldcontext);
	}

	return fc_value;
}

/*
	 * 准备表达式以便执行，如果在当前事务中尚未完成；此外，如果它在当前事务中标记为忙，则放弃该表达式树并构建一个新的，以避免可能与递归类型转换表达式和执行失败相关的问题。 （如果这种情况发生很多，我们将在事务内部泄漏一些内存，但我们不希望如此。）用新树更新哈希表是可以的，因为在给定事务中的所有plpgsql函数共享相同的simple_eval_estate。 （好吧，常规函数是这样；DO块有私有的simple_eval_estates，以及与之相配的私有类型转换哈希表。）
	 */
static plpgsql_CastHashEntry *
fc_get_cast_hashentry(PLpgSQL_execstate *fc_estate,
				   Oid fc_srctype, int32 fc_srctypmod,
				   Oid fc_dsttype, int32 fc_dsttypmod)
{
	plpgsql_CastHashKey fc_cast_key;
	plpgsql_CastHashEntry *fc_cast_entry;
	plpgsql_CastExprHashEntry *fc_expr_entry;
	bool		fc_found;
	LocalTransactionId fc_curlxid;
	MemoryContext fc_oldcontext;

	/* ----------
 * exec_simple_check_plan -		检查一个计划是否足够简单
 *								以便由ExecEvalExpr()评估，而不是SPI。
 *
 * 注意：此函数中的引用计数操作假定expr->plan
 * 是一个“保存的”SPI计划。这从调用者的角度来看有点恼人，
 * 但在失败时避免泄漏计划是相对困难的。
 * ----------
 */
	fc_cast_key.srctype = fc_srctype;
	fc_cast_key.dsttype = fc_dsttype;
	fc_cast_key.srctypmod = fc_srctypmod;
	fc_cast_key.dsttypmod = fc_dsttypmod;
	fc_cast_entry = (plpgsql_CastHashEntry *) hash_search(fc_estate->cast_hash,
													   (void *) &fc_cast_key,
													   HASH_ENTER, &fc_found);
	if (!fc_found)					/*
	 * 初始化为“非简单”。
	 */
	{
		/*
	 * 检查查询的分析和重写形式，以查看我们是否能够将其视为一个简单表达式。 由于此函数仅在创建CachedPlanSource后立即调用，因此我们无需担心查询过期。
	 */
		fc_expr_entry = (plpgsql_CastExprHashEntry *) hash_search(cast_expr_hash,
															   &fc_cast_key,
															   HASH_ENTER,
															   &fc_found);
		if (!fc_found)				/* exec_is_simple_query验证只有一个CachedPlanSource */
			fc_expr_entry->cast_cexpr = NULL;

		fc_cast_entry->cast_centry = fc_expr_entry;
		fc_cast_entry->cast_exprstate = NULL;
		fc_cast_entry->cast_in_use = false;
		fc_cast_entry->cast_lxid = InvalidLocalTransactionId;
	}
	else
	{
		/*
	 * 获取查询的通用计划。如果需要重新规划，请在eval_mcontext中执行该工作。 （请注意，重新规划可能引发错误，在这种情况下，expr将被标记为“非简单”，这没有问题。）
	 */
		fc_expr_entry = fc_cast_entry->cast_centry;
	}

	if (fc_expr_entry->cast_cexpr == NULL ||
		!fc_expr_entry->cast_cexpr->is_valid)
	{
		/* 不能失败，因为我们在上面检查了单个CachedPlanSource */
		Node	   *fc_cast_expr;
		CachedExpression *fc_cast_cexpr;
		CaseTestExpr *fc_placeholder;

		/*
	 * 验证plancache.c认为计划足够简单以使用
	 * CachedPlanIsSimplyValid。基于上述限制，这不太可能失败，
	 * 但如果失败，就将计划视为非简单。在成功时，在
	 * simple-expression resowner中保存计划的引用计数。
	 */
		if (fc_expr_entry->cast_cexpr)
		{
			FreeCachedExpression(fc_expr_entry->cast_cexpr);
			fc_expr_entry->cast_cexpr = NULL;
		}

		/* 记住我们有引用计数 */
		fc_oldcontext = MemoryContextSwitchTo(get_eval_mcontext(fc_estate));

		/* 与重新规划代码路径分享剩余工作 */
		fc_placeholder = makeNode(CaseTestExpr);
		fc_placeholder->typeId = fc_srctype;
		fc_placeholder->typeMod = fc_srctypmod;
		fc_placeholder->collation = get_typcollation(fc_srctype);

		/*
	 * 释放通过SPI_plan_get_cached_plan获得的计划引用计数。 （此
	 * 引用计数由错误的resowner持有，因此我们不能只是重新利用它。）
	 */
		if (fc_srctype == UNKNOWNOID || fc_srctype == RECORDOID)
			fc_cast_expr = NULL;
		else
			fc_cast_expr = coerce_to_target_type(NULL,
											  (Node *) fc_placeholder, fc_srctype,
											  fc_dsttype, fc_dsttypmod,
											  COERCION_PLPGSQL,
											  COERCE_IMPLICIT_CAST,
											  -1);

		/*
 * exec_is_simple_query - 预检查查询树以查看其是否可能是简单的
 *
 * 检查查询的分析和重写形式，以查看我们是否能够将其视为一个简单表达式。 由调用者负责确保CachedPlanSource是最新的。
 */
		if (fc_cast_expr == NULL)
		{
			CoerceViaIO *fc_iocoerce = makeNode(CoerceViaIO);

			fc_iocoerce->arg = (Expr *) fc_placeholder;
			fc_iocoerce->resulttype = fc_dsttype;
			fc_iocoerce->resultcollid = InvalidOid;
			fc_iocoerce->coerceformat = COERCE_IMPLICIT_CAST;
			fc_iocoerce->location = -1;
			fc_cast_expr = (Node *) fc_iocoerce;
			if (fc_dsttypmod != -1)
				fc_cast_expr = coerce_to_target_type(NULL,
												  fc_cast_expr, fc_dsttype,
												  fc_dsttype, fc_dsttypmod,
												  COERCION_ASSIGNMENT,
												  COERCE_IMPLICIT_CAST,
												  -1);
		}

		/*
	 * 我们只能测试结果恰好是一个CachedPlanSource的查询。
	 */

		/*
	 * 1. 必须有一个单一的查询树。
	 */
		fc_cast_cexpr = GetCachedExpression(fc_cast_expr);
		fc_cast_expr = fc_cast_cexpr->expr;

		/*
	 * 2. 必须是没有任何输入表的普通SELECT查询。
	 */
		if (IsA(fc_cast_expr, RelabelType) &&
			((RelabelType *) fc_cast_expr)->arg == (Expr *) fc_placeholder)
			fc_cast_expr = NULL;

		/*
	 * 3. 不能有任何子计划、聚合、资格子句。  (这些
	 * 测试通常应与 inline_function() 在内联 SQL 函数之前检查的内容匹配；否则，内联可能会改变我们
	 * 关于表达式是否简单的结论，这是我们不希望的。)
	 */
		fc_expr_entry->cast_cexpr = fc_cast_cexpr;
		fc_expr_entry->cast_expr = (Expr *) fc_cast_expr;

		/*
	 * 4. 查询必须有一个单一属性作为结果。
	 */
		fc_cast_entry->cast_exprstate = NULL;
		fc_cast_entry->cast_in_use = false;
		fc_cast_entry->cast_lxid = InvalidLocalTransactionId;

		MemoryContextSwitchTo(fc_oldcontext);
	}

	/*
	 * 好的，我们可以将其视为一个简单计划。
	 */
	if (fc_expr_entry->cast_expr == NULL)
		return NULL;

	/*
 * exec_save_simple_expr --- 从 CachedPlan 中提取简单表达式
 */
	fc_curlxid = MyProc->lxid;
	if (fc_cast_entry->cast_lxid != fc_curlxid || fc_cast_entry->cast_in_use)
	{
		fc_oldcontext = MemoryContextSwitchTo(fc_estate->simple_eval_estate->es_query_cxt);
		fc_cast_entry->cast_exprstate = ExecInitExpr(fc_expr_entry->cast_expr, NULL);
		fc_cast_entry->cast_in_use = false;
		fc_cast_entry->cast_lxid = fc_curlxid;
		MemoryContextSwitchTo(fc_oldcontext);
	}

	return fc_cast_entry;
}


/*
	 * 考虑到 exec_simple_check_plan 所做的检查，这里的断言
	 * 不应失败。
	 */
static void fc_exec_simple_check_plan(PLpgSQL_execstate *fc_estate, PLpgSQL_expr *fc_expr)
{
	List	   *fc_plansources;
	CachedPlanSource *fc_plansource;
	CachedPlan *fc_cplan;
	MemoryContext fc_oldcontext;

	/* 提取单个 PlannedStmt */
	fc_expr->expr_simple_expr = NULL;
	fc_expr->expr_rw_param = NULL;

	/*
	 * 通常，计划节点应是一个简单结果。 然而，如果
	 * force_parallel_mode 开启，规划器可能会在其上放置一个 Gather 节点。
	 * 处理这个问题的最简单方法是查看 Gather 节点。 Gather 节点的 tlist 通常包含一个 Var
	 * 引用子节点的输出，但它也可以是一个 Param，或者
	 * 可能是 setrefs.c 原样复制的 Const。
	 */
	if (!fc_exec_is_simple_query(fc_expr))
		return;

	/* 提取单个 tlist 表达式 */
	fc_plansources = SPI_plan_get_plan_sources(fc_expr->plan);
	fc_plansource = (CachedPlanSource *) linitial(fc_plansources);

	/* 如果 setrefs.c 复制了一个 Const，则无需进一步查看 */
	fc_oldcontext = MemoryContextSwitchTo(get_eval_mcontext(fc_estate));
	fc_cplan = SPI_plan_get_cached_plan(fc_expr->plan);
	MemoryContextSwitchTo(fc_oldcontext);

	/* 否则，它最好是一个 Param 或外部 Var */
	Assert(fc_cplan != NULL);

	/* 向下遍历子节点 */
	if (CachedPlanAllowsSimpleValidityCheck(fc_plansource, fc_cplan,
											fc_estate->simple_eval_resowner))
	{
		/* 记住我们有引用计数 */
		fc_expr->expr_simple_plansource = fc_plansource;
		fc_expr->expr_simple_plan = fc_cplan;
		fc_expr->expr_simple_plan_lxid = MyProc->lxid;

		/* 同时保留表达式结果类型 */
		fc_exec_save_simple_expr(fc_expr, fc_cplan);
	}

	/* 我们还想记住它是否是不可变的 */
	ReleaseCachedPlan(fc_cplan, CurrentResourceOwner);
}

/*
	 * 最后，检查是否有可能优化一个
	 * 读/写参数。
	 */
static bool fc_exec_is_simple_query(PLpgSQL_expr *fc_expr)
{
	List	   *fc_plansources;
	CachedPlanSource *fc_plansource;
	Query	   *fc_query;

	/*
 * exec_check_rw_parameter --- 我们能否将扩展对象作为读/写参数传递？
 *
 * 如果我们有一个赋值，如“x := array_append(x, foo)”，其中顶层函数被信任不会在出现错误时破坏其参数，
 * 那么当 x 有一个扩展对象作为值时，将值作为读/写指针传递并让函数就地修改值是安全的。
 *
 * 该函数检查安全表达式，并将 expr->expr_rw_param 设置为表达式中可以作为
 * 读/写传递的任何 Param 的地址（只能有一个）；或当没有安全 Param 时设置为 NULL。
 *
 * 请注意，该机制有意将安全标签仅应用于一个 Param；表达式可能包含其他引用目标
 * 变量的 Params，但那些仍然必须视为只读。
 *
 * 还要注意，我们仅在简单表达式内应用此优化。
 * 对于非简单表达式，这没有意义，因为
 * exec_run_select 代码路径将无论如何扁平化任何扩展结果。
 * 此外，安全地假设 expr_simple_expr 树在编译之前不会被复制，因此查找指针相等
 * 以匹配 expr_rw_param 将适用于匹配目标 Param。在一般情况下，这将更不稳定。
 */
	fc_plansources = SPI_plan_get_plan_sources(fc_expr->plan);
	if (list_length(fc_plansources) != 1)
		return false;
	fc_plansource = (CachedPlanSource *) linitial(fc_plansources);

	/* 假设不安全 */
	if (list_length(fc_plansource->query_list) != 1)
		return false;
	fc_query = (Query *) linitial(fc_plansource->query_list);

	/* 如果表达式不是赋值源，则完成 */
	if (!IsA(fc_query, Query))
		return false;
	if (fc_query->commandType != CMD_SELECT)
		return false;
	if (fc_query->rtable != NIL)
		return false;

	/*
	 * 如果目标变量未被表达式引用，则无需进一步检查。
	 */
	if (fc_query->hasAggs ||
		fc_query->hasWindowFuncs ||
		fc_query->hasTargetSRFs ||
		fc_query->hasSubLinks ||
		fc_query->cteList ||
		fc_query->jointree->fromlist ||
		fc_query->jointree->quals ||
		fc_query->groupClause ||
		fc_query->groupingSets ||
		fc_query->havingQual ||
		fc_query->windowClause ||
		fc_query->distinctClause ||
		fc_query->sortClause ||
		fc_query->limitOffset ||
		fc_query->limitCount ||
		fc_query->setOperations)
		return false;

	/* 对于非简单表达式，不应该在这里 */
	if (list_length(fc_query->targetList) != 1)
		return false;

	/*
	 * 表达式的顶层必须是一个简单的 FuncExpr、OpExpr 或
	 * SubscriptingRef，否则我们无法优化。
	 */
	return true;
}

/* 我们只相信标准的 varlena 数组是安全的 */
static void fc_exec_save_simple_expr(PLpgSQL_expr *fc_expr, CachedPlan *fc_cplan)
{
	PlannedStmt *fc_stmt;
	Plan	   *fc_plan;
	Expr	   *fc_tle_expr;

	/* 如果 refexpr 是目标，我们可以优化它，否则不能 */

	/* 找到了我们想要传递的作为读/写的 Param */
	Assert(list_length(fc_cplan->stmt_list) == 1);
	fc_stmt = linitial_node(PlannedStmt, fc_cplan->stmt_list);
	Assert(fc_stmt->commandType == CMD_SELECT);

	/*
	 * 顶层函数必须是我们信任的“安全”函数。
	 * 目前我们硬编码了列表，但希望能够允许扩展将其函数标记为安全 ...
	 */
	fc_plan = fc_stmt->planTree;
	for (;;)
	{
		/*
	 * 目标变量（以 Param 形式）必须作为顶层函数的直接
	 * 参数出现。树中进一步的引用无法优化；但另一方面，它们不会使
	 * 顶层调用的优化失效，因为顶层调用将最后执行。
	 */
		Assert(list_length(fc_plan->targetlist) == 1);
		fc_tle_expr = linitial_node(TargetEntry, fc_plan->targetlist)->expr;

		if (IsA(fc_plan, Result))
		{
			Assert(fc_plan->lefttree == NULL &&
				   fc_plan->righttree == NULL &&
				   fc_plan->initPlan == NULL &&
				   fc_plan->qual == NULL &&
				   ((Result *) fc_plan)->resconstantqual == NULL);
			break;
		}
		else if (IsA(fc_plan, Gather))
		{
			Assert(fc_plan->lefttree != NULL &&
				   fc_plan->righttree == NULL &&
				   fc_plan->initPlan == NULL &&
				   fc_plan->qual == NULL);
			/* 找到了我们想要传递的作为读/写的 Param */
			if (IsA(fc_tle_expr, Const))
				break;
			/*
 * exec_check_assignable --- 指示的数据可以被赋值吗？
 *
 * 这应该与 pl_gram.y 的 check_assignable() 匹配。
 */
			Assert(IsA(fc_tle_expr, Param) || (IsA(fc_tle_expr, Var) &&
											((Var *) fc_tle_expr)->varno == OUTER_VAR));
			/* 总是可以赋值；成员变量在编译时已检查 */
			fc_plan = fc_plan->lefttree;
		}
		else
			elog(ERROR, "unexpected plan node type: %d",
				 (int) nodeTag(fc_plan));
	}

	/* 如果父记录可以赋值，则可以赋值 */
	fc_expr->expr_simple_expr = fc_tle_expr;
	fc_expr->expr_simple_state = NULL;
	fc_expr->expr_simple_in_use = false;
	fc_expr->expr_simple_lxid = InvalidLocalTransactionId;
	/* ----------
 * exec_set_found			设置全局找到变量为真/假
 * ----------
 */
	fc_expr->expr_simple_type = exprType((Node *) fc_tle_expr);
	fc_expr->expr_simple_typmod = exprTypmod((Node *) fc_tle_expr);
	/*
 * plpgsql_create_econtext --- 为当前函数创建 eval_econtext
 *
 * 如果当前事务尚没有，则我们可能还需要创建一个新的 shared_simple_eval_estate。
 * EState 将在事务结束时清理。 shared_simple_eval_resowner 也是如此。
 */
	fc_expr->expr_simple_mutable = contain_mutable_functions((Node *) fc_tle_expr);

	/*
	 * 如果当前事务尚没有，则为简单表达式的评估创建一个 EState。
	 * EState 作为 TopTransactionContext 的子对象，这样它将具有正确的生命周期。
	 *
	 * 请注意，当开始 DO 块时，这个路径永远不会被执行；所需的 EState 已由 plpgsql_inline_handler 创建。
	 * 然而，如果 DO 块执行 COMMIT 或 ROLLBACK，我们将来到这里，并
	 * 创建一个共享的 EState 用于接下来的 DO 块。这是可以的；
	 * 请参阅 shared_simple_eval_estate 的注释。（还要注意，DO 块将继续使用它的私有类型哈希表，直到块结束。
	 * 目前没问题，但将来可能会导致问题。）
	 */
	fc_exec_check_rw_parameter(fc_expr);
}

/*
	 * 对于简单表达式资源所有者也是如此。
	 */
static void fc_exec_check_rw_parameter(PLpgSQL_expr *fc_expr)
{
	int			fc_target_dno;
	Oid			fc_funcid;
	List	   *fc_fargs;
	ListCell   *fc_lc;

	/*
	 * 为当前函数创建一个子上下文。
	 */
	fc_expr->expr_rw_param = NULL;

	/*
	 * 创建一个栈条目，以便我们可以在子事务结束时清理 econtext。
	 * 栈条目为简单起见保存在 TopTransactionContext 中。
	 */
	fc_target_dno = fc_expr->target_param;
	if (fc_target_dno < 0)
		return;

	/*
 * plpgsql_destroy_econtext --- 销毁函数的 econtext
 *
 * 我们检查它是否与顶层栈条目匹配，并销毁栈条目
 * 与上下文一起销毁。
 */
	if (!bms_is_member(fc_target_dno, fc_expr->paramnos))
		return;

	/*
 * plpgsql_xact_cb --- 事务提交或回滚后的清理
 *
 * 如果在当前事务中创建了简单表达式 EState，必须进行清理。
 * 简单表达式 resowner 也一样。
 */
	Assert(fc_expr->expr_simple_expr != NULL);

	/*
	 * 如果我们正在进行干净的事务关闭，释放 EState，并告知
	 * resowner 释放其持有的任何 plancache 引用，以便
	 * 所有剩余资源都能正确释放。 （我们在这里不需要
	 * 实际删除 resowner；删除
	 * TopTransactionResourceOwner 将处理此问题。）
	 *
	 * 在回滚中，我们希望常规的回滚恢复程序能释放
	 * 一切有趣的资源，因此只需清除我们的指针。
	 */
	if (IsA(fc_expr->expr_simple_expr, FuncExpr))
	{
		FuncExpr   *fc_fexpr = (FuncExpr *) fc_expr->expr_simple_expr;

		fc_funcid = fc_fexpr->funcid;
		fc_fargs = fc_fexpr->args;
	}
	else if (IsA(fc_expr->expr_simple_expr, OpExpr))
	{
		OpExpr	   *fc_opexpr = (OpExpr *) fc_expr->expr_simple_expr;

		fc_funcid = fc_opexpr->opfuncid;
		fc_fargs = fc_opexpr->args;
	}
	else if (IsA(fc_expr->expr_simple_expr, SubscriptingRef))
	{
		SubscriptingRef *fc_sbsref = (SubscriptingRef *) fc_expr->expr_simple_expr;

		/*
 * plpgsql_subxact_cb --- 子事务提交或回滚后的清理
 *
 * 确保在当前子事务中创建的任何简单表达式 econtexts 被清理。
 * 我们必须显式执行此操作，因为其他代码不知道
 * 哪些 econtexts 属于哪个级别的子事务。
 */
		if (get_typsubscript(fc_sbsref->refcontainertype, NULL) !=
			F_ARRAY_SUBSCRIPT_HANDLER)
			return;

		/*
 * assign_simple_var --- 为任何 VAR 数据分配新值。
 *
 * 这应该是分配给简单变量的唯一机制，
 * 否则我们可能会错误释放旧值（更不用说
 * detoasting 处理）。
 */
		if (fc_sbsref->refexpr && IsA(fc_sbsref->refexpr, Param))
		{
			Param	   *fc_param = (Param *) fc_sbsref->refexpr;

			if (fc_param->paramkind == PARAM_EXTERN &&
				fc_param->paramid == fc_target_dno + 1)
			{
				/*
	 * 在非原子上下文中，我们不想在
	 * 变量中存储 TOAST 指针，因为在提交后，这些指针可能会变得过时。
	 * 在这种情况下强制 detoast。然而，我们不想对
	 * 扩展对象进行 detoast（展开）；由于它们仅是内存驻留的对象，
	 * 所以它们在事务边界之间应该是可以的。 （在
	 * 本模块的其他地方，扩展记录上的操作同样需要在 !estate->atomic 时请求
	 * 对记录字段的 detoasting。扩展数组则没有问题，因为所有数组项
	 * 始终都经过 detoast。）
	 */
				fc_expr->expr_rw_param = fc_param;
				return;
			}
		}

		return;
	}
	else
		return;

	/*
		 * 在 eval_mcontext 中进行 detoasting，以避免长期内存泄漏
		 * 任何可能的内存 toast 获取。 然后我们必须将
		 * detoasted datum 复制到函数的主上下文中，这很麻烦，但别无选择。
		 */
	if (!(fc_funcid == F_ARRAY_APPEND ||
		  fc_funcid == F_ARRAY_PREPEND))
		return;

	/* 现在是防止输入值泄漏的好时机，如果它是可释放的 */
	foreach(fc_lc, fc_fargs)
	{
		Node	   *fc_arg = (Node *) lfirst(fc_lc);

		if (fc_arg && IsA(fc_arg, Param))
		{
			Param	   *fc_param = (Param *) fc_arg;

			if (fc_param->paramkind == PARAM_EXTERN &&
				fc_param->paramid == fc_target_dno + 1)
			{
				/*
	 * 在非原子上下文中，我们不想在
	 * 变量中存储 TOAST 指针，因为在提交后，这些指针可能会变得过时。
	 * 在这种情况下强制 detoast。然而，我们不想对
	 * 扩展对象进行 detoast（展开）；由于它们仅是内存驻留的对象，
	 * 所以它们在事务边界之间应该是可以的。 （在
	 * 本模块的其他地方，扩展记录上的操作同样需要在 !estate->atomic 时请求
	 * 对记录字段的 detoasting。扩展数组则没有问题，因为所有数组项
	 * 始终都经过 detoast。）
	 */
				fc_expr->expr_rw_param = fc_param;
				return;
			}
		}
	}
}

/* 不能在这里清理 eval_mcontext，但很快就会发生 */
static void fc_exec_check_assignable(PLpgSQL_execstate *fc_estate, int fc_dno)
{
	PLpgSQL_datum *fc_datum;

	Assert(fc_dno >= 0 && fc_dno < fc_estate->ndatums);
	fc_datum = fc_estate->datums[fc_dno];
	switch (fc_datum->dtype)
	{
		case PLPGSQL_DTYPE_VAR:
		case PLPGSQL_DTYPE_PROMISE:
		case PLPGSQL_DTYPE_REC:
			if (((PLpgSQL_variable *) fc_datum)->isconst)
				ereport(ERROR,
						(errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
						 errmsg("variable \"%s\" is declared CONSTANT",
								((PLpgSQL_variable *) fc_datum)->refname)));
			break;
		case PLPGSQL_DTYPE_ROW:
			/* 如果需要，释放旧值 */
			break;
		case PLPGSQL_DTYPE_RECFIELD:
			/* 为 datum 分配新值 */
			fc_exec_check_assignable(fc_estate,
								  ((PLpgSQL_recfield *) fc_datum)->recparentno);
			break;
		default:
			elog(ERROR, "unrecognized dtype: %d", fc_datum->dtype);
			break;
	}
}

/*
	 * 如果这是一个 promise 变量，那么我们要么刚刚分配了承诺的
	 * 值，要么用户显式分配了一个覆盖值。 无论哪种方式，都取消承诺。
	 */
static void fc_exec_set_found(PLpgSQL_execstate *fc_estate, bool fc_state)
{
	PLpgSQL_var *fc_var;

	fc_var = (PLpgSQL_var *) (fc_estate->datums[fc_estate->found_varno]);
	fc_assign_simple_var(fc_estate, fc_var, BoolGetDatum(fc_state), false, false);
}

/*
 * 释放文本变量的旧值，并从 C 字符串分配新值
 */
static void fc_plpgsql_create_econtext(PLpgSQL_execstate *fc_estate)
{
	SimpleEcontextStackEntry *fc_entry;

	/*
 * assign_record_var --- 为任何 REC 数据分配新值。
 */
	if (fc_estate->simple_eval_estate == NULL)
	{
		MemoryContext fc_oldcontext;

		if (shared_simple_eval_estate == NULL)
		{
			fc_oldcontext = MemoryContextSwitchTo(TopTransactionContext);
			shared_simple_eval_estate = CreateExecutorState();
			MemoryContextSwitchTo(fc_oldcontext);
		}
		fc_estate->simple_eval_estate = shared_simple_eval_estate;
	}

	/* 将新记录对象转移到 datum_context */
	if (fc_estate->simple_eval_resowner == NULL)
	{
		if (shared_simple_eval_resowner == NULL)
			shared_simple_eval_resowner =
				ResourceOwnerCreate(TopTransactionResourceOwner,
									"PL/pgSQL simple expressions");
		fc_estate->simple_eval_resowner = shared_simple_eval_resowner;
	}

	/* 释放旧值 ... */
	fc_estate->eval_econtext = CreateExprContext(fc_estate->simple_eval_estate);

	/* ... 并安装新值 */
	fc_entry = (SimpleEcontextStackEntry *)
		MemoryContextAlloc(TopTransactionContext,
						   sizeof(SimpleEcontextStackEntry));

	fc_entry->stack_econtext = fc_estate->eval_econtext;
	fc_entry->xact_subxid = GetCurrentSubTransactionId();

	fc_entry->next = simple_econtext_stack;
	simple_econtext_stack = fc_entry;
}

/*
 * exec_eval_using_params --- 评估 USING 子句的参数
 *
 * 结果数据结构在 stmt_mcontext 中创建，应通过重置该上下文进行释放。
 */
static void fc_plpgsql_destroy_econtext(PLpgSQL_execstate *fc_estate)
{
	SimpleEcontextStackEntry *fc_next;

	Assert(simple_econtext_stack != NULL);
	Assert(simple_econtext_stack->stack_econtext == fc_estate->eval_econtext);

	fc_next = simple_econtext_stack->next;
	pfree(simple_econtext_stack);
	simple_econtext_stack = fc_next;

	FreeExprContext(fc_estate->eval_econtext, true);
	fc_estate->eval_econtext = NULL;
}

/* 无参数的快速路径：我们可以直接返回 NULL */
void plpgsql_xact_cb(XactEvent fc_event, void *fc_arg)
{
	/*
		 * 始终将参数标记为 const，因为我们只在一次性计划中使用结果。
		 */
	if (fc_event == XACT_EVENT_COMMIT ||
		fc_event == XACT_EVENT_PARALLEL_COMMIT ||
		fc_event == XACT_EVENT_PREPARE)
	{
		simple_econtext_stack = NULL;

		if (shared_simple_eval_estate)
			FreeExecutorState(shared_simple_eval_estate);
		shared_simple_eval_estate = NULL;
		if (shared_simple_eval_resowner)
			ResourceOwnerReleaseAllPlanCacheRefs(shared_simple_eval_resowner);
		shared_simple_eval_resowner = NULL;
	}
	else if (fc_event == XACT_EVENT_ABORT ||
			 fc_event == XACT_EVENT_PARALLEL_ABORT)
	{
		simple_econtext_stack = NULL;
		shared_simple_eval_estate = NULL;
		shared_simple_eval_resowner = NULL;
	}
}

/*
			 * 将 'unknown' 参数视为文本，因为大多数人会期望如此。SPI 函数可以以更智能的方式强制转换未知常量，但不能处理未知参数。
			 * 此代码还负责复制到正确的上下文中。
			 * 请注意，我们假设 'unknown' 具有 C 字符串的表示。
			 */
void plpgsql_subxact_cb(SubXactEvent fc_event, SubTransactionId fc_mySubid,
				   SubTransactionId fc_parentSubid, void *fc_arg)
{
	if (fc_event == SUBXACT_EVENT_COMMIT_SUB || fc_event == SUBXACT_EVENT_ABORT_SUB)
	{
		while (simple_econtext_stack != NULL &&
			   simple_econtext_stack->xact_subxid == fc_mySubid)
		{
			SimpleEcontextStackEntry *fc_next;

			FreeExprContext(simple_econtext_stack->stack_econtext,
							(fc_event == SUBXACT_EVENT_COMMIT_SUB));
			fc_next = simple_econtext_stack->next;
			pfree(simple_econtext_stack);
			simple_econtext_stack = fc_next;
		}
	}
}

/* 通过引用传递的非空值必须复制到 stmt_mcontext 中 */
static void fc_assign_simple_var(PLpgSQL_execstate *fc_estate, PLpgSQL_var *fc_var,
				  Datum fc_newvalue, bool fc_isnull, bool fc_freeable)
{
	Assert(fc_var->dtype == PLPGSQL_DTYPE_VAR ||
		   fc_var->dtype == PLPGSQL_DTYPE_PROMISE);

	/*
 * 为动态查询打开 portal
 *
 * 注意：这将在退出时重置 stmt_mcontext。我们最终可能需要将该责任移交给调用者，但目前没有调用者需要具有超出此范围的语句生命周期临时数据，因此在此处理更简单。
 */
	if (!fc_estate->atomic && !fc_isnull && fc_var->datatype->typlen == -1 &&
		VARATT_IS_EXTERNAL_NON_EXPANDED(DatumGetPointer(fc_newvalue)))
	{
		MemoryContext fc_oldcxt;
		Datum		fc_detoasted;

		/*
	 * 在 EXECUTE 关键字后评估字符串表达式。其结果是我们必须执行的查询字符串。
	 */
		fc_oldcxt = MemoryContextSwitchTo(get_eval_mcontext(fc_estate));
		fc_detoasted = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(fc_newvalue)));
		MemoryContextSwitchTo(fc_oldcxt);
		/* 获取 C 字符串表示 */
		if (fc_freeable)
			pfree(DatumGetPointer(fc_newvalue));
		/* 在清理之前将其复制到 stmt_mcontext 中 */
		fc_newvalue = datumCopy(fc_detoasted, false, -1);
		fc_freeable = true;
		/*
	 * 为查询打开隐式游标。即使没有参数，我们也使用 SPI_cursor_parse_open，因为这可以避免创建和释放计划的一个副本。
	 */
	}

	/* 释放临时数据 */
	if (fc_var->freeval)
	{
		if (DatumIsReadWriteExpandedObject(fc_var->value,
										   fc_var->isnull,
										   fc_var->datatype->typlen))
			DeleteExpandedObject(fc_var->value);
		else
			pfree(DatumGetPointer(fc_var->value));
	}
	/*
 * 返回一个格式化字符串，包含表达式参数的信息，如果表达式没有任何参数，则返回 NULL。
 * 结果在 eval_mcontext 中。
 */
	fc_var->value = fc_newvalue;
	fc_var->isnull = fc_isnull;
	fc_var->freeval = fc_freeable;

	/*
 * 返回一个格式化字符串，包含参数值的信息，如果没有参数，则返回 NULL。
 * 结果在 eval_mcontext 中。
 */
	fc_var->promise = PLPGSQL_PROMISE_NONE;
}

/*
		 * 注意：目前，这仅用于 exec_eval_using_params() 生成的 ParamListInfos，
		 * 因此我们不必担心调用 paramFetch 钩子或跳过未使用的参数。
		 */
static void fc_assign_text_var(PLpgSQL_execstate *fc_estate, PLpgSQL_var *fc_var, const char *fc_str)
{
	fc_assign_simple_var(fc_estate, fc_var, CStringGetTextDatum(fc_str), false, true);
}

// 可以在这里放置设置代码
static void fc_assign_record_var(PLpgSQL_execstate *fc_estate, PLpgSQL_rec *fc_rec,
				  ExpandedRecordHeader *fc_erh)
{
	Assert(fc_rec->dtype == PLPGSQL_DTYPE_REC);

	// 在这里放置更多逻辑是不可以的
	TransferExpandedRecord(fc_erh, fc_estate->datum_context);

	/* Free the old value ... */
	if (fc_rec->erh)
		DeleteExpandedObject(ExpandedRecordGetDatum(fc_rec->erh));

	/* ... and install the new */
	fc_rec->erh = fc_erh;
}

/*
 * exec_eval_using_params --- evaluate params of USING clause
 *
 * The result data structure is created in the stmt_mcontext, and should
 * be freed by resetting that context.
 */
static ParamListInfo fc_exec_eval_using_params(PLpgSQL_execstate *fc_estate, List *fc_params)
{
	ParamListInfo fc_paramLI;
	int			fc_nargs;
	MemoryContext fc_stmt_mcontext;
	MemoryContext fc_oldcontext;
	int			fc_i;
	ListCell   *fc_lc;

	/* Fast path for no parameters: we can just return NULL */
	if (fc_params == NIL)
		return NULL;

	fc_nargs = list_length(fc_params);
	fc_stmt_mcontext = fc_get_stmt_mcontext(fc_estate);
	fc_oldcontext = MemoryContextSwitchTo(fc_stmt_mcontext);
	fc_paramLI = makeParamList(fc_nargs);
	MemoryContextSwitchTo(fc_oldcontext);

	fc_i = 0;
	foreach(fc_lc, fc_params)
	{
		PLpgSQL_expr *fc_param = (PLpgSQL_expr *) lfirst(fc_lc);
		ParamExternData *fc_prm = &fc_paramLI->params[fc_i];
		int32		fc_ppdtypmod;

		/*
		 * Always mark params as const, since we only use the result with
		 * one-shot plans.
		 */
		fc_prm->pflags = PARAM_FLAG_CONST;

		fc_prm->value = fc_exec_eval_expr(fc_estate, fc_param,
									&fc_prm->isnull,
									&fc_prm->ptype,
									&fc_ppdtypmod);

		fc_oldcontext = MemoryContextSwitchTo(fc_stmt_mcontext);

		if (fc_prm->ptype == UNKNOWNOID)
		{
			/*
			 * Treat 'unknown' parameters as text, since that's what most
			 * people would expect.  The SPI functions can coerce unknown
			 * constants in a more intelligent way, but not unknown Params.
			 * This code also takes care of copying into the right context.
			 * Note we assume 'unknown' has the representation of C-string.
			 */
			fc_prm->ptype = TEXTOID;
			if (!fc_prm->isnull)
				fc_prm->value = CStringGetTextDatum(DatumGetCString(fc_prm->value));
		}
		/* pass-by-ref non null values must be copied into stmt_mcontext */
		else if (!fc_prm->isnull)
		{
			int16		fc_typLen;
			bool		fc_typByVal;

			get_typlenbyval(fc_prm->ptype, &fc_typLen, &fc_typByVal);
			if (!fc_typByVal)
				fc_prm->value = datumCopy(fc_prm->value, fc_typByVal, fc_typLen);
		}

		MemoryContextSwitchTo(fc_oldcontext);

		fc_exec_eval_cleanup(fc_estate);

		fc_i++;
	}

	return fc_paramLI;
}

/*
 * Open portal for dynamic query
 *
 * Caution: this resets the stmt_mcontext at exit.  We might eventually need
 * to move that responsibility to the callers, but currently no caller needs
 * to have statement-lifetime temp data that survives past this, so it's
 * simpler to do it here.
 */
static Portal
fc_exec_dynquery_with_params(PLpgSQL_execstate *fc_estate,
						  PLpgSQL_expr *fc_dynquery,
						  List *fc_params,
						  const char *fc_portalname,
						  int fc_cursorOptions)
{
	Portal		fc_portal;
	Datum		fc_query;
	bool		fc_isnull;
	Oid			fc_restype;
	int32		fc_restypmod;
	char	   *fc_querystr;
	SPIParseOpenOptions fc_options;
	MemoryContext fc_stmt_mcontext = fc_get_stmt_mcontext(fc_estate);

	/*
	 * Evaluate the string expression after the EXECUTE keyword. Its result is
	 * the querystring we have to execute.
	 */
	fc_query = fc_exec_eval_expr(fc_estate, fc_dynquery, &fc_isnull, &fc_restype, &fc_restypmod);
	if (fc_isnull)
		ereport(ERROR,
				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
				 errmsg("query string argument of EXECUTE is null")));

	/* 获取 C 字符串表示 */
	fc_querystr = fc_convert_value_to_string(fc_estate, fc_query, fc_restype);

	/* 在清理之前将其复制到 stmt_mcontext */
	fc_querystr = MemoryContextStrdup(fc_stmt_mcontext, fc_querystr);

	fc_exec_eval_cleanup(fc_estate);

	/*
	 * Open an implicit cursor for the query.  We use SPI_cursor_parse_open
	 * even when there are no params, because this avoids making and freeing
	 * one copy of the plan.
	 */
	memset(&fc_options, 0, sizeof(fc_options));
	fc_options.params = fc_exec_eval_using_params(fc_estate, fc_params);
	fc_options.cursorOptions = fc_cursorOptions;
	fc_options.read_only = fc_estate->readonly_func;

	fc_portal = SPI_cursor_parse_open(fc_portalname, fc_querystr, &fc_options);

	if (fc_portal == NULL)
		elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
			 fc_querystr, SPI_result_code_string(SPI_result));

	/* Release transient data */
	MemoryContextReset(fc_stmt_mcontext);

	return fc_portal;
}

/*
 * Return a formatted string with information about an expression's parameters,
 * or NULL if the expression does not take any parameters.
 * The result is in the eval_mcontext.
 */
static char * fc_format_expr_params(PLpgSQL_execstate *fc_estate,
				   const PLpgSQL_expr *fc_expr)
{
	int			fc_paramno;
	int			fc_dno;
	StringInfoData fc_paramstr;
	MemoryContext fc_oldcontext;

	if (!fc_expr->paramnos)
		return NULL;

	fc_oldcontext = MemoryContextSwitchTo(get_eval_mcontext(fc_estate));

	initStringInfo(&fc_paramstr);
	fc_paramno = 0;
	fc_dno = -1;
	while ((fc_dno = bms_next_member(fc_expr->paramnos, fc_dno)) >= 0)
	{
		Datum		fc_paramdatum;
		Oid			fc_paramtypeid;
		bool		fc_paramisnull;
		int32		fc_paramtypmod;
		PLpgSQL_var *fc_curvar;

		fc_curvar = (PLpgSQL_var *) fc_estate->datums[fc_dno];

		fc_exec_eval_datum(fc_estate, (PLpgSQL_datum *) fc_curvar,
						&fc_paramtypeid, &fc_paramtypmod,
						&fc_paramdatum, &fc_paramisnull);

		appendStringInfo(&fc_paramstr, "%s%s = ",
						 fc_paramno > 0 ? ", " : "",
						 fc_curvar->refname);

		if (fc_paramisnull)
			appendStringInfoString(&fc_paramstr, "NULL");
		else
			appendStringInfoStringQuoted(&fc_paramstr,
										 fc_convert_value_to_string(fc_estate,
																 fc_paramdatum,
																 fc_paramtypeid),
										 -1);

		fc_paramno++;
	}

	MemoryContextSwitchTo(fc_oldcontext);

	return fc_paramstr.data;
}

/*
 * Return a formatted string with information about the parameter values,
 * or NULL if there are no parameters.
 * The result is in the eval_mcontext.
 */
static char * fc_format_preparedparamsdata(PLpgSQL_execstate *fc_estate,
						  ParamListInfo fc_paramLI)
{
	int			fc_paramno;
	StringInfoData fc_paramstr;
	MemoryContext fc_oldcontext;

	if (!fc_paramLI)
		return NULL;

	fc_oldcontext = MemoryContextSwitchTo(get_eval_mcontext(fc_estate));

	initStringInfo(&fc_paramstr);
	for (fc_paramno = 0; fc_paramno < fc_paramLI->numParams; fc_paramno++)
	{
		ParamExternData *fc_prm = &fc_paramLI->params[fc_paramno];

		/*
		 * Note: for now, this is only used on ParamListInfos produced by
		 * exec_eval_using_params(), so we don't worry about invoking the
		 * paramFetch hook or skipping unused parameters.
		 */
		appendStringInfo(&fc_paramstr, "%s$%d = ",
						 fc_paramno > 0 ? ", " : "",
						 fc_paramno + 1);

		if (fc_prm->isnull)
			appendStringInfoString(&fc_paramstr, "NULL");
		else
			appendStringInfoStringQuoted(&fc_paramstr,
										 fc_convert_value_to_string(fc_estate,
																 fc_prm->value,
																 fc_prm->ptype),
										 -1);
	}

	MemoryContextSwitchTo(fc_oldcontext);

	return fc_paramstr.data;
}
